透過 NestedScrollView 源碼解析嵌套滑動原理,Android View 的事件分發原理解析_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

NestedScrollView 是用於替代 ScrollView 來解決嵌套滑動過程中的滑動事件的衝突。作為開發者,你會發現很多地方會用到嵌套滑動的邏輯,比如下拉刷新頁面,京東或者淘寶的各種商品頁面。

那為什麼要去了解 NestedScrollView 的源碼呢?那是因為 NestedScrollView 是嵌套滑動實現的模板範例,通過研讀它的源碼,能夠讓你知道如何實現嵌套滑動,然後如果需求上 NestedScrollView 無法滿足的時候,你可以自定義。

嵌套滑動

說到嵌套滑動,就得說說這兩個類了:NestedScrollingParent3 和 NestedScrollingChild3 ,當然同時也存在後面不帶数字的類。之所以後面帶数字了,是為了解決之前的版本遺留的問題:fling 的時候涉及嵌套滑動,無法透傳到另一個View 上繼續 fling,導致滑動效果大打折扣 。

其實 NestedScrollingParent2 相比 NestedScrollingParent 在方法調用上多了一個參數 type,用於標記這個滑動是如何產生的。type 的取值如下:

    /**
     * Indicates that the input type for the gesture is from a user touching the screen. 觸摸產生的滑動
     */
    public static final int TYPE_TOUCH = 0;

    /**
     * Indicates that the input type for the gesture is caused by something which is not a user
     * touching a screen. This is usually from a fling which is settling.  簡單理解就是fling
     */
    public static final int TYPE_NON_TOUCH = 1;

嵌套滑動,說得通俗點就是子 view 和 父 view 在滑動過程中,互相通信決定某個滑動是子view 處理合適,還是 父view 來處理。所以, Parent 和 Child 之間存在相互調用,遵循下面的調用關係:

上圖可以這麼理解:

  • ACTION_DOWN 的時候子 view 就要調用 startNestedScroll( ) 方法來告訴父 view 自己要開始滑動了(實質上是尋找能夠配合 child 進行嵌套滾動的 parent),parent 也會繼續向上尋找能夠配合自己滑動的 parent,可以理解為在做一些準備工作 。
  • 父 view 會收到 onStartNestedScroll 回調從而決定是不是要配合子 view 做出響應。如果需要配合,此方法會返回 true。繼而 onStartNestedScroll()回調會被調用。
  • 在滑動事件產生但是子 view 還沒處理前可以調用 dispatchNestedPreScroll(0,dy,consumed,offsetInWindow) 這個方法把事件傳給父 view,這樣父 view 就能在onNestedPreScroll 方法裏面收到子 view 的滑動信息,然後做出相應的處理把處理完后的結果通過 consumed 傳給子 view。

  • dispatchNestedPreScroll()之後,child可以進行自己的滾動操作。

  • 如果父 view 需要在子 view 滑動后處理相關事件的話可以在子 view 的事件處理完成之後調用 dispatchNestedScroll 然後父 view 會在 onNestedScroll 收到回調。

  • 最後,滑動結束,調用 onStopNestedScroll() 表示本次處理結束。

  • 但是,如果滑動速度比較大,會觸發 fling, fling 也分為 preFling 和 fling 兩個階段,處理過程和 scroll 基本差不多。 

NestedScrollView

首先是看類的名字

 class NestedScrollView extends FrameLayout implements NestedScrollingParent3,
 NestedScrollingChild3, ScrollingView {

可以發現它繼承了 FrameLayout,相當於它就是一個 ViewGroup,可以添加子 view , 但是需要注意的事,它只接受一個子 view,否則會報錯。

    @Override
    public void addView(View child) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child);
    }

    @Override
    public void addView(View child, int index) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, params);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index, params);
    }

add view

對於 NestedScrollingParent3,NestedScrollingChild3 的作用,前文已經說了,如果還是不理解,後面再對源碼的分析過程中也會分析到。

其實這裏還可以提一下 RecyclerView:

public class RecyclerView extends ViewGroup implements ScrollingView,
        NestedScrollingChild2, NestedScrollingChild3 {

這裏沒有繼承 NestedScrollingParent3 是因為開發者覺得 RecyclerView 適合做一個子類。並且它的功能作為一個列表去展示,也就是不適合再 RecyclerView 內部去做一些複雜的嵌套滑動之類的。這樣 RecycylerView 外層就可以再嵌套一個 NestedScrollView 進行嵌套滑動了。後面再分析嵌套滑動的時候,也會把 RecycylerView 當作子類來進行分析,這樣能更好的理解源碼。

內部有個接口,使用者需要對滑動變化進行監聽的,可以添加這個回調:

    public interface OnScrollChangeListener {
        /**
         * Called when the scroll position of a view changes.
         *
         * @param v The view whose scroll position has changed.
         * @param scrollX Current horizontal scroll origin.
         * @param scrollY Current vertical scroll origin.
         * @param oldScrollX Previous horizontal scroll origin.
         * @param oldScrollY Previous vertical scroll origin.
         */
        void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
                int oldScrollX, int oldScrollY);
    }

構造函數

下面來看下構造函數:

    public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initScrollView();

        final TypedArray a = context.obtainStyledAttributes(
                attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0);
        // 是否要鋪滿全屏
        setFillViewport(a.getBoolean(0, false));

        a.recycle();
        // 即是子類,又是父類
        mParentHelper = new NestedScrollingParentHelper(this);
        mChildHelper = new NestedScrollingChildHelper(this);

        // ...because why else would you be using this widget? 默認是滾動,不然你使用它就沒有意義了
        setNestedScrollingEnabled(true);

        ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE);
    }    

這裏我們用了兩個輔助類來幫忙處理嵌套滾動時候的一些邏輯處理,NestedScrollingParentHelper,NestedScrollingChildHelper。這個是和前面的你實現的接口 NestedScrollingParent3,NestedScrollingChild3 相對應的。

下面看下  initScrollView 方法里的具體邏輯:

    private void initScrollView() {
        mScroller = new OverScroller(getContext());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
     // 會調用 ViewGroup 的 onDraw setWillNotDraw(
false); // 獲取 ViewConfiguration 中一些配置,包括滑動距離,最大最小速率等等 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); }

setFillViewport

在構造函數中,有這麼一個設定:

setFillViewport(a.getBoolean(0, false));

與 setFillViewport 對應的屬性是 android:fillViewport=”true”。如果不設置這個屬性為 true,可能會出現如下圖一樣的問題:

xml 布局:

<?xml version="1.0" encoding="utf-8"?>
<NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#fff000">
        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>
</NestedScrollView>

效果:

可以發現這個沒有鋪滿全屏,可是 xml 明明已經設置了 match_parent 了。這是什麼原因呢?

那為啥設置 true 就可以了呢?下面來看下它的 onMeasure 方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // false 直接返回
        if (!mFillViewport) {
            return;
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            return;
        }

        if (getChildCount() > 0) {
            View child = getChildAt(0);
            final NestedScrollView.LayoutParams lp = (LayoutParams) child.getLayoutParams();

            int childSize = child.getMeasuredHeight();
            int parentSpace = getMeasuredHeight()
                    - getPaddingTop()
                    - getPaddingBottom()
                    - lp.topMargin
                    - lp.bottomMargin;
            // 如果子 view 高度小於 父 view 高度,那麼需要重新設定高度
            if (childSize < parentSpace) {
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin,
                        lp.width);
                // 這裏生成 MeasureSpec 傳入的是 parentSpace,並且用的是 MeasureSpec.EXACTLY 
                int childHeightMeasureSpec =
                        MeasureSpec.makeMeasureSpec(parentSpace, MeasureSpec.EXACTLY);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

當你將 mFillViewport 設置為 true 后,就會把父 View 高度給予子 view 。可是這個解釋了設置 mFillViewport 可以解決不能鋪滿屏幕的問題,可是沒有解決為啥 match_parent 無效的問題。

在回到類的繼承關係上,NestedScrollView 繼承的是 FrameLayout,也就是說,FrameLayout 應該和 NestedScrollView 擁有一樣的問題。可是當你把 xml 中的布局換成 FrameLayout 后,你發現竟然沒有問題。那麼這是為啥呢?

原因是 NestedScrollView 又重寫了 measureChildWithMargins 。子view 的 childHeightMeasureSpec 中的 mode 是 MeasureSpec.UNSPECIFIED 。當被設置為這個以後,子 view 的高度就完全是由自身的高度決定了。

    @Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        // 在生成子 view 的 MeasureSpec 時候,傳入的是 MeasureSpec.UNSPECIFIED
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

比如子 view 是 LinearLayout ,這時候,它的高度就是子 view 的高度之和。而且,這個 MeasureSpec.UNSPECIFIED 會一直影響着後面的子子孫孫 view 。

我猜這麼設計的目的是因為你既然使用了 NestedScrollView,就沒必要在把子 View  搞得跟屏幕一樣大了,它該多大就多大,不然你滑動的時候,看見一大片空白體驗也不好啊。

而 ViewGroup 中,measureChildWithMargins 的方法是這樣的:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

由於一般使用 NestedScrollView 的時候,都是會超過屏幕高度的,所以不設置這個屬性為 true 也沒有關係。

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

繪製

既然前面已經把 onMeasure 講完了,那索引把繪製這塊都講了把。下面是 draw 方法,這裏主要是繪製邊界的陰影:

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (mEdgeGlowTop != null) {
            final int scrollY = getScrollY();
       // 上邊界陰影繪製
if (!mEdgeGlowTop.isFinished()) { final int restoreCount = canvas.save(); int width = getWidth(); int height = getHeight(); int xTranslation = 0; int yTranslation = Math.min(0, scrollY); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { width -= getPaddingLeft() + getPaddingRight(); xTranslation += getPaddingLeft(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { height -= getPaddingTop() + getPaddingBottom(); yTranslation += getPaddingTop(); } canvas.translate(xTranslation, yTranslation); mEdgeGlowTop.setSize(width, height); if (mEdgeGlowTop.draw(canvas)) { ViewCompat.postInvalidateOnAnimation(this); } canvas.restoreToCount(restoreCount); }
       // 底部邊界陰影繪製
if (!mEdgeGlowBottom.isFinished()) { final int restoreCount = canvas.save(); int width = getWidth(); int height = getHeight(); int xTranslation = 0; int yTranslation = Math.max(getScrollRange(), scrollY) + height; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { width -= getPaddingLeft() + getPaddingRight(); xTranslation += getPaddingLeft(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { height -= getPaddingTop() + getPaddingBottom(); yTranslation -= getPaddingBottom(); } canvas.translate(xTranslation - width, yTranslation); canvas.rotate(180, width, 0); mEdgeGlowBottom.setSize(width, height); if (mEdgeGlowBottom.draw(canvas)) { ViewCompat.postInvalidateOnAnimation(this); } canvas.restoreToCount(restoreCount); } } }

onDraw 是直接用了父類的,這個沒啥好講的,下面看看 onLayout:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mIsLayoutDirty = false;
        // Give a child focus if it needs it
        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
            scrollToChild(mChildToScrollTo);
        }
        mChildToScrollTo = null;

        if (!mIsLaidOut) { // 是否是第一次調用onLayout // If there is a saved state, scroll to the position saved in that state.
            if (mSavedState != null) {
                scrollTo(getScrollX(), mSavedState.scrollPosition);
                mSavedState = null;
            } // mScrollY default value is "0"

            // Make sure current scrollY position falls into the scroll range.  If it doesn't,
            // scroll such that it does.
            int childSize = 0;
            if (getChildCount() > 0) {
                View child = getChildAt(0);
                NestedScrollView.LayoutParams lp = (LayoutParams) child.getLayoutParams();
                childSize = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            }
            int parentSpace = b - t - getPaddingTop() - getPaddingBottom();
            int currentScrollY = getScrollY();
            int newScrollY = clamp(currentScrollY, parentSpace, childSize);
            if (newScrollY != currentScrollY) {
                scrollTo(getScrollX(), newScrollY);
            }
        }

        // Calling this with the present values causes it to re-claim them
        scrollTo(getScrollX(), getScrollY());
        mIsLaidOut = true;
    }

onLayout 方法也沒什麼說的,基本上是用了父類 FrameLayout 的布局方法,加入了一些 scrollTo 操作滑動到指定位置。

嵌套滑動分析

如果對滑動事件不是很清楚的小夥伴可以先看看這篇文章:Android View 的事件分發原理解析。

在分析之前,先做一個假設,比如 RecyclerView 就是 NestedScrollView 的子類,這樣去分析嵌套滑動更容易理解。這時候,用戶點擊 RecyclerView 觸發滑動。需要分析整個滑動過程的事件傳遞。

dispatchTouchEvent

這裏,NestedScrollView 用的是父類的處理,並沒有添加自己的邏輯。

onInterceptTouchEvent

當事件進行分發前,ViewGroup 首先會調用 onInterceptTouchEvent 詢問自己要不要進行攔截,不攔截,就會分發傳遞給子 view。一般來說,對於 ACTION_DOWN 都不會攔截,這樣子類有機會獲取事件,只有子類不處理,才會再次傳給父 View 來處理。下面來看看其具體代碼邏輯:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.
        */
        final int action = ev.getAction();
     // 如果已經在拖動了,說明已經在滑動了,直接返回 true
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. 不是一個有效的id break; } final int pointerIndex = ev.findPointerIndex(activePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent"); break; } final int y = (int) ev.getY(pointerIndex);
          // 計算垂直方向上滑動的距離
final int yDiff = Math.abs(y - mLastMotionY);
          // 確定可以產生滾動了
if (yDiff > mTouchSlop && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists();
            // 可以獲取滑動速率 mVelocityTracker.addMovement(ev); mNestedYOffset
= 0; final ViewParent parent = getParent(); if (parent != null) {
               // 讓父 view 不要攔截,這裏應該是為了保險起見,因為既然已經走進來了,只要你返回 true,父 view 就不會攔截了。 parent.requestDisallowInterceptTouchEvent(
true); } } break; } case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY();
          // 如果點擊的範圍不在子 view 上,直接break,比如自己設置了很大的 margin,此時用戶點擊這裏,這個範圍理論上是不參与滑動的
if (!inChild((int) ev.getX(), y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; } /* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionY = y; mActivePointerId = ev.getPointerId(0);           // 在收到 DOWN 事件的時候,做一些初始化的工作 initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); /* * If being flinged and user touches the screen, initiate drag; * otherwise don't. mScroller.isFinished should be false when * being flinged. We need to call computeScrollOffset() first so that * isFinished() is correct. */ mScroller.computeScrollOffset();
          // 如果此時正在fling, isFinished 會返回 flase mIsBeingDragged
= !mScroller.isFinished();
          // 開始滑動 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); }
          // 手抬起后,停止滑動 stopNestedScroll(ViewCompat.TYPE_TOUCH);
break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; }

onInterceptTouchEvent 事件就是做一件事,決定事件是不是要繼續交給自己的 onTouchEvent 處理。這裏需要注意的一點是,如果子 view 在 dispatchTouchEvent 中調用了:

parent.requestDisallowInterceptTouchEvent(true)

那麼,其實就不會再調用 onInterceptTouchEvent 方法。也就是說上面的邏輯就不會走了。但是可以發現,down 事件,一般是不會攔截的。但是如果正在 fling,此時就會返回 true,直接把事件全部攔截。

那看下 RecyclerView 的 dispatchTouchEvent 是父類的,沒啥好分析的。而且它的 onInterceptTouchEvent 也是做了一些初始化的一些工作,和 NestedScrollView 一樣沒啥可說的。

onTouchEvent

再說 NestedScrollView 的 onTouchEvent。

對於 onTouchEvent 得分兩類進行討論,如果其子 view 不是 ViewGroup ,且是不可點擊的,就會把事件直接交給 NestedScrollView 來處理。

但是如果點擊的子 view 是 RecyclerView 的 ViewGroup 。當 down 事件來的時候,ViewGroup 的子 view 沒有處理,那麼就會交給 ViewGroup 來處理,你會發現ViewGroup 的 onTouchEvent 是默認返回 true 的。也就是說事件都是由  RecyclerView 來處理的。

這時候來看下 NestedScrollView 的 onTouchEvent 代碼:

 public boolean onTouchEvent(MotionEvent ev) {
        initVelocityTrackerIfNotExists();

        MotionEvent vtev = MotionEvent.obtain(ev);

        final int actionMasked = ev.getActionMasked();

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
          // 需要有一個子類才可以進行滑動
if (getChildCount() == 0) { return false; }
          // 前面提到如果用戶在 fling 的時候,觸碰,此時是直接攔截返回 true,自己來處理事件。
if ((mIsBeingDragged = !mScroller.isFinished())) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged.處理結果就是停止 fling */ if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // Remember where the motion event started mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0);
         // 尋找嵌套父View,告訴它準備在垂直方向上進行 TOUCH 類型的滑動 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break; } case MotionEvent.ACTION_MOVE: final int activePointerIndex = ev.findPointerIndex(mActivePointerId); if (activePointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); break; } final int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y;
          // 滑動前先把移動距離告訴嵌套父View,看看它要不要消耗,返回 true 代表消耗了部分距離
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) { deltaY -= mScrollConsumed[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; }
          // 滑動距離大於最大最小觸發距離
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); }
            // 觸發滑動 mIsBeingDragged
= true; if (deltaY > 0) { deltaY -= mTouchSlop; } else { deltaY += mTouchSlop; } } if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; final int oldY = getScrollY(); final int range = getScrollRange(); final int overscrollMode = getOverScrollMode(); boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); // Calling overScrollByCompat will call onOverScrolled, which // calls onScrollChanged if applicable.
            // 該方法會觸發自身內容的滾動
if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0, 0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } final int scrolledDeltaY = getScrollY() - oldY; final int unconsumedY = deltaY - scrolledDeltaY;
            // 通知嵌套的父 View 我已經處理完滾動了,該你來處理了
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset, ViewCompat.TYPE_TOUCH)) {
              // 如果嵌套父View 消耗了滑動,那麼需要更新 mLastMotionY
-= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } else if (canOverscroll) { ensureGlows(); final int pulledToY = oldY + deltaY;
               // 觸發邊緣的陰影效果
if (pulledToY < 0) { EdgeEffectCompat.onPull(mEdgeGlowTop, (float) deltaY / getHeight(), ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else if (pulledToY > range) { EdgeEffectCompat.onPull(mEdgeGlowBottom, (float) deltaY / getHeight(), 1.f - ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } } if (mEdgeGlowTop != null && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { ViewCompat.postInvalidateOnAnimation(this); } } } break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
          // 計算滑動速率
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
          // 大於最小的設定的速率,觸發fling
if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } mActivePointerId = INVALID_POINTER; endDrag(); break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } } mActivePointerId = INVALID_POINTER; endDrag(); break; case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); mLastMotionY = (int) ev.getY(index); mActivePointerId = ev.getPointerId(index); break; } case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); break; } if (mVelocityTracker != null) { mVelocityTracker.addMovement(vtev); } vtev.recycle(); return true; }

ACTION_DOWN

先看 down 事件,如果處於 fling 期間,那麼直接停止 fling, 接着會調用 startNestedScroll,會讓 NestedScrollView 作為子 view 去 通知嵌套父 view,那麼就需要找到有沒有可以嵌套滑動的父 view 。

    public boolean startNestedScroll(int axes, int type) {
        // 交給 mChildHelper 代理來處理相關邏輯
        return mChildHelper.startNestedScroll(axes, type);
    }


    public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        // 找到嵌套父 view 了,就直接返回
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        // 是否支持嵌套滾動
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {  // while 循環,將支持嵌套滑動的父 View 找出來。
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    // 把父 view 設置進去
                    setNestedScrollingParentForType(type, p);
                    // 找到后,通過該方法可以做一些初始化操作
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }            

可以看到,這時候主要就是為了找到嵌套父 view。當 ViewParentCompat.onStartNestedScroll 返回 true,就表示已經找到嵌套滾動的父 View 了 。下面來看下這個方法的具體邏輯:

    // ViewParentCompat  
    public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                    return parent.onStartNestedScroll(child, target, nestedScrollAxes);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onStartNestedScroll", e);
                }
            } else if (parent instanceof NestedScrollingParent) {
                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
                        nestedScrollAxes);
            }
        }
        return false;
    }

這裏其實沒啥好分析,就是告訴父類當前是什麼類型的滾動,以及滾動方向。其實這裏可以直接看下 NestedScrollView 的 onStartNestedScroll 的邏輯。

//  NestedScrollView
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
            int type) {
     // 確保觸發的是垂直方向的滾動
return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; }

當確定了嵌套父 View 以後,又會調用父 view 的  onNestedScrollAccepted 方法,在這裏可以做一些準備工作和配置。下面我們看到的 是 Ns 裏面的方法,注意不是父 view 的,只是當作參考。

public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
    mParentHelper.onNestedScrollAccepted(child, target, axes, type);
   // 這裏 Ns 作為子 view 調用 該方法去尋找嵌套父 view。注意這個方法會被調用是 NS 作為父 view 收到的。這樣就 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type); }

到這裏,down 的作用就講完了。

ACTION_MOVE 

首先是會調用 dispatchNestedPreScroll,講當前的滑動距離告訴嵌套父 View。

  public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
            int type) {
     // Ns 作為子 view 去通知父View
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type); } 

下面看下 mChildHelper 的代碼邏輯:

    /**
     * Dispatch one step of a nested pre-scrolling operation to the current nested scrolling parent.
     *
     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
     * method/{@link androidx.core.view.NestedScrollingChild2} interface method with the same
     * signature to implement the standard policy.</p>
     *
     * @return true if the parent consumed any of the nested scroll
     */
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow, @NestedScrollType int type) {
        if (isNestedScrollingEnabled()) {
       // 獲取之前找到的嵌套滾動的父 View
final ViewParent parent = getNestedScrollingParentForType(type); if (parent == null) { return false; }        // 滑動距離肯定不為0 才有意義 if (dx != 0 || dy != 0) { int startX = 0; int startY = 0; if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); startX = offsetInWindow[0]; startY = offsetInWindow[1]; } if (consumed == null) { if (mTempNestedScrollConsumed == null) { mTempNestedScrollConsumed = new int[2]; } consumed = mTempNestedScrollConsumed; } consumed[0] = 0; consumed[1] = 0;
          // 調用嵌套父 View 的對應的回調 ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; } return consumed[0] != 0 || consumed[1] != 0; } else if (offsetInWindow != null) { offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }

這裏主要是將滑動距離告訴 父 view,有消耗就會返回 true 。

    // ViewParentCompat
    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
            int[] consumed) {
        onNestedPreScroll(parent, target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
    }

其實下面的 onNestedPreScroll 跟前面的 onStartNestedScroll 邏輯很像,就是層層傳遞。

    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
            int[] consumed, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            ((NestedScrollingParent2) parent).onNestedPreScroll(target, dx, dy, consumed, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                    parent.onNestedPreScroll(target, dx, dy, consumed);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onNestedPreScroll", e);
                }
            } else if (parent instanceof NestedScrollingParent) {
                ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);
            }
        }
    }

下面為了方便,沒法查看 NS 的嵌套父 View 的邏輯。直接看 Ns 中對應的方法。

    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
            int type) {
     // 最終也是 Ns 再傳給其嵌套父 View dispatchNestedPreScroll(dx, dy, consumed,
null, type); }

傳遞完了之後,就會調用  overScrollByCompat 來實現滾動。

    boolean overScrollByCompat(int deltaX, int deltaY,
            int scrollX, int scrollY,
            int scrollRangeX, int scrollRangeY,
            int maxOverScrollX, int maxOverScrollY,
            boolean isTouchEvent) {
        final int overScrollMode = getOverScrollMode();
        final boolean canScrollHorizontal =
                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
        final boolean canScrollVertical =
                computeVerticalScrollRange() > computeVerticalScrollExtent();
        final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
        final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);

        int newScrollX = scrollX + deltaX;
        if (!overScrollHorizontal) {
            maxOverScrollX = 0;
        }

        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }

        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;

        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }

        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }

        if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
            mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
        }
     
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);

        return clampedX || clampedY;
    }

整塊邏輯其實沒啥好說的,然後主要是看 onOverScrolled 這個方法:

   protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
        super.scrollTo(scrollX, scrollY);
    }

最終是調用 scrollTo 方法來實現了滾動。

當滾動完了后,會調用 dispatchNestedScroll 告訴父 view 當前還剩多少沒消耗,如果是 0,那麼就不會上傳,如果沒消耗完,就會傳給父 View 。

如果是子 View 傳給 NS 的,是會通過 scrollBy 來進行消耗的,然後繼續向上層傳遞。

    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
            int dyUnconsumed, int type) {
        final int oldScrollY = getScrollY();
        scrollBy(0, dyUnconsumed);
        final int myConsumed = getScrollY() - oldScrollY;
        final int myUnconsumed = dyUnconsumed - myConsumed;
        dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null,
                type);
    }

假設當前已經滑動到頂部了,此時繼續滑動的話,就會觸發邊緣的陰影效果。

ACTION_UP

當用戶手指離開后,如果滑動速率超過最小的滑動速率,就會調用 flingWithNestedDispatch(-initialVelocity) ,下面來看看這個方法的具體邏輯:

    private void flingWithNestedDispatch(int velocityY) {
        final int scrollY = getScrollY();
        final boolean canFling = (scrollY > 0 || velocityY > 0)
                && (scrollY < getScrollRange() || velocityY < 0);
     // fling 前問問父View 要不要 fling, 一般是返回 false
if (!dispatchNestedPreFling(0, velocityY)) {
       // 這裏主要是告訴父類打算自己消耗了 dispatchNestedFling(
0, velocityY, canFling);
       // 自己處理 fling(velocityY); } }

下面繼續看 fling 的實現。

    public void fling(int velocityY) {
        if (getChildCount() > 0) {

            mScroller.fling(getScrollX(), getScrollY(), // start
                    0, velocityY, // velocities
                    0, 0, // x
                    Integer.MIN_VALUE, Integer.MAX_VALUE, // y
                    0, 0); // overscroll
            runAnimatedScroll(true);
        }
    }

    private void runAnimatedScroll(boolean participateInNestedScrolling) {
        if (participateInNestedScrolling) {
            // fling 其實也是一種滾動,只不過是非接觸的
            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
        } else {
            stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
        }
        mLastScrollerY = getScrollY();
        ViewCompat.postInvalidateOnAnimation(this);
    }

最終會觸發重繪操作,重繪過程中會調用 computeScroll,下面看下其內部的代碼邏輯。

    @Override
    public void computeScroll() {

        if (mScroller.isFinished()) {
            return;
        }

        mScroller.computeScrollOffset();
        final int y = mScroller.getCurrY();
        int unconsumed = y - mLastScrollerY;
        mLastScrollerY = y;

        // Nested Scrolling Pre Pass
        mScrollConsumed[1] = 0;
     // 滾動的時候,依然會把當前的未消耗的滾動距離傳給嵌套父View dispatchNestedPreScroll(
0, unconsumed, mScrollConsumed, null, ViewCompat.TYPE_NON_TOUCH); unconsumed -= mScrollConsumed[1]; final int range = getScrollRange(); if (unconsumed != 0) { // Internal Scroll final int oldScrollY = getScrollY();
       // 自己消耗 overScrollByCompat(
0, unconsumed, getScrollX(), oldScrollY, 0, range, 0, 0, false); final int scrolledByMe = getScrollY() - oldScrollY; unconsumed -= scrolledByMe; // Nested Scrolling Post Pass mScrollConsumed[1] = 0;
        // 繼續上傳給父View dispatchNestedScroll(
0, scrolledByMe, 0, unconsumed, mScrollOffset, ViewCompat.TYPE_NON_TOUCH, mScrollConsumed); unconsumed -= mScrollConsumed[1]; }      // 如果到這裡有未消耗的,說明已經滾動到邊緣了 if (unconsumed != 0) { final int mode = getOverScrollMode(); final boolean canOverscroll = mode == OVER_SCROLL_ALWAYS || (mode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); if (canOverscroll) { ensureGlows(); if (unconsumed < 0) { if (mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); } } else { if (mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); } } }
       // 停止滾動   abortAnimatedScroll(); }      // 如果此時滾動還未結束,並且當前的滑動距離都被消耗了,那麼繼續刷新滾動,直到停止為止
if (!mScroller.isFinished()) { ViewCompat.postInvalidateOnAnimation(this); } }

到這裏,關於 Ns 的嵌套滑動就講完了。希望大家能夠對嵌套滑動有個理解。

閱讀 Ns 的源碼,可以讓你更好的理解嵌套滑動,以及事件分發的邏輯。

  本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

【Python】組合數據類型_網頁設計公司

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

集合類型

集合類型定義

集合是多個元素的無序組合

  • 集合類型與數學中的集合概念一致
  • 集合元素之間無序,每個元素唯一,不存在相同元素
  • 集合元素不可更改,不能是可變數據類型

    理解:因為集合類型不重複,所以不能更改,否則有可能重複。

集合是多個元素的無序組合

  • 集合用大括號 {} 表示,元素間用逗號分隔
  • 建立集合類型用 {}set()
  • 建立空集合類型,必須使用set()

集合操作符

操作符及應用 描述
S | T 並,返回一個新集合,包括在集合S和T中的所有元素
S – T 差,返回一個新集合,包括在集合S但不在T中的元素
S & T 交,返回一個新集合,包括同時在集合S和T中的元素
S ^ T 補,返回一個新集合,包括集合S和T中的非相同元素
S <= T 返回True/False,判斷S和T的子集關係
S < T 返回True/False,判斷S和T的子集關係
S >= T 返回True/False,判斷S和T的包含關係
S > T 返回True/False,判斷S和T的包含關係
S |= T 並,更新集合S,包括在集合S和T中的所有元素
S -= T 差,更新集合S,包括在集合S但不在T中的元素
S &= T 交,更新集合S,包括同時在集合S和T中的元素
S ^= T 補,更新集合S,包括集合S和T中的非相同元素

集合處理方法

操作函數或方法 描述
S.add(x) 如果x不在集合S中,將x增加到S
S.discard(x) 移除S中元素x,如果x不在集合S中,不報錯
S.remove(x) 移除S中元素x,如果x不在集合S中,產生KeyError異常
S.clear() 移除S中所有元素
S.pop() 隨機返回S的一個元素,更新S,若S為空產生KeyError異常
S.copy() 返回集合S的一個副本
len(S) 返回集合S的元素個數
x in S 判斷S中元素x,x在集合S中,返回True,否則返回False
x not in S 判斷S中元素x,x不在集合S中,返回True,否則返回False
set(x) 將其他類型變量x轉變為集合類型

集合類型應用場景

數據去重:集合類型所有元素無重複

序列類型

序列類型定義

序列是具有先後關係的一組元素

  • 序列是一維元素向量,元素類型可以不同
  • 類似數學元素序列: s0, s1, … , sn-1
  • 元素間由序號引導,通過下標訪問序列的特定元素

序列處理函數及方法

操作符及應用 描述
x in s 如果x是序列s的元素,返回True,否則返回False
x not in s 如果x是序列s的元素,返回False,否則返回True
s + t 連接兩個序列s和t
s*n 或 n*s 將序列s複製n次
s[i] 索引,返回s中的第i個元素,i是序列的序號
s[i: j]
s[i: j: k]
切片,返回序列s中第i到j以k為步長的元素子序列
函數和方法 描述
len(s) 返回序列s的長度,即元素個數
min(s) 返回序列s的最小元素,s中元素需要可比較
max(s) 返回序列s的最大元素,s中元素需要可比較
s.index(x)
s.index(x, i, j)
返回序列s從i開始到j位置中第一次出現元素x的位置
s.count(x) 返回序列s中出現x的總次數

元組類型及操作

元組是序列類型的一種擴展

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

  • 元組是一種序列類型,一旦創建就不能被修改
  • 使用小括號 ()tuple() 創建,元素間用逗號 , 分隔
  • 可以使用或不使用小括號

元組繼承序列類型的全部通用操作

  • 元組繼承了序列類型的全部通用操作
  • 元組因為創建后不能修改,因此沒有特殊操作
  • 使用或不使用小括號

列表類型及操作

列表是序列類型的一種擴展,十分常用

  • 列表是一種序列類型,創建后可以隨意被修改
  • 使用方括號 [] 或list() 創建,元素間用逗號 , 分隔
  • 列表中各元素類型可以不同,無長度限制
函數或方法 描述
ls[i] = x 替換列表ls第i元素為x
ls[i: j: k] = lt 用列表lt替換ls切片后所對應元素子列表
del ls[i] 刪除列表ls中第i元素
del ls[i: j: k] 刪除列表ls中第i到第j以k為步長的元素
ls += lt 更新列表ls,將列表lt元素增加到列表ls中
ls *= n 更新列表ls,其元素重複n次
函數或方法 描述
ls.append(x) 在列表ls最後增加一個元素x
ls.clear() 刪除列表ls中所有元素
ls.copy() 生成一個新列表,賦值ls中所有元素
ls.insert(i,x) 在列表ls的第i位置增加元素x
ls.pop(i) 將列表ls中第i位置元素取出並刪除該元素
ls.remove(x) 將列表ls中出現的第一個元素x刪除
ls.reverse() 將列表ls中的元素反轉

序列類型應用場景

數據表示:元組 和 列表

  • 元組用於元素不改變的應用場景,更多用於固定搭配場景
  • 列表更加靈活,它是最常用的序列類型
  • 最主要作用:表示一組有序數據,進而操作它們

元素遍歷

數據保護

  • 如果不希望數據被程序所改變,轉換成元組類型

字典

字典類型定義

  • 映射是一種鍵(索引)和值(數據)的對應
  • 鍵值對:鍵是數據索引的擴展
  • 字典是鍵值對的集合,鍵值對之間無序
  • 採用大括號{}dict()創建,鍵值對用冒號: 表示

{<鍵1>:<值1>, <鍵2>:<值2>, … , <鍵n>:<值n>}

<字典變量> = {<鍵1>:<值1>, … , <鍵n>:<值n>}
<值> = <字典變量>[<鍵>]
<字典變量>[<鍵>] = <值>
[ ] 用來向字典變量中索引或增加元素

字典處理函數及方法

函數或方法 描述
del d[k] 刪除字典d中鍵k對應的數據值
k in d 判斷鍵k是否在字典d中,如果在返回True,否則False
d.keys() 返回字典d中所有的鍵信息
d.values() 返回字典d中所有的值信息
d.items() 返回字典d中所有的鍵值對信息
d.get(k, <default>) 鍵k存在,則返回相應值,不在則返回 值
d.pop(k, <default>) 鍵k存在,則取出相應值,不在則返回 值
d.popitem() 隨機從字典d中取出一個鍵值對,以元組形式返回
d.clear() 刪除所有的鍵值對
len(d) 返回字典d中元素的個數

字典類型應用場景

映射的表達

  • 映射無處不在,鍵值對無處不在
  • 例如:統計數據出現的次數,數據是鍵,次數是值
  • 最主要作用:表達鍵值對數據,進而操作它們

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

文物里的唐都長安人生活_貨運

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

  展出的文官俑。

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

  参觀者在觀展。

  近日,陝西西安博物院“樂居長安—唐都長安人的生活”展開展。本次展覽從西安博物院11萬餘件館藏文物中,特別遴選出280餘件/組唐代精品文物,分別圍繞唐長安的“城、人、衣、食、行、娛”等內容,對唐都長安人的生活進行了全面的再現和闡釋。新華社記者 劉瀟攝

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

盛世修典,築起民間文化長城_網頁設計公司

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

  民間藝人在進行藝術表演。資料圖片

  中國民間文學大系出版工程首批成果——《中國民間文學大系》12個示範卷 資料圖片

  2019年12月25日,中國民間文學大系出版工程(以下簡稱“大系出版工程”)首批成果發布會在人民大會堂舉行,發布了《中國民間文學大系》(以下簡稱《大系》)的12個示範卷,涉及神話、史詩、傳說、故事、歌謠、長詩、說唱、小戲、諺語、謎語、俗語和理論12個門類,共計1200多萬字。作為大系出版工程的成果,《大系》文庫是我國有史以來記錄民間文學數量最多、內容最豐富、種類最齊全、形式最多樣、最具活態性的文庫。

  最大規模的民間文學出版工程

  2017年1月,中辦國辦印發《關於實施中華優秀傳統文化傳承發展工程的意見》。作為《關於實施中華優秀傳統文化傳承發展工程的意見》的15個重點工程之一,大系出版工程在中宣部、中國文聯的領導下,由中國民間文藝家協會團結民間文學領域的專家學者具體實施。

  《大系》涉及神話、史詩、傳說、故事、歌謠、長詩、說唱、小戲、諺語、謎語、俗語、理論12大門類。首批出版的12個示範卷各門類分別一本,每本100萬字左右,共計1200多萬字、300餘幅圖片。12個示範卷分別為《神話·雲南卷(一)》《史詩·黑龍江卷·伊瑪堪分卷》《傳說·吉林卷(一)》《故事·河南卷·平頂山分卷》《歌謠·四川卷·漢族分卷》《長詩·雲南卷(一)》《說唱·遼寧卷(一)》《小戲·湖南卷·影戲分卷》《諺語·河北卷》《謎語·河南卷(一)》《俗語·江蘇卷(一)》《理論(2000—2018)·第一卷(總論)》。

  《大系》所收作品按照科學性、廣泛性、地域性、代表性的原則編選,在田野普查、文字記錄、圖片拍攝和音頻視頻等信息採集以及查閱大量歷史資料的基礎上,強調學術規範,把握民間文學的“活態性、生活性、歷史性和文化性”,注重《大系》內容的全面性、代表性、真實性,多維度、多向度、全方位展現了民間文學的歷史風貌與新時代人文精神。

  示範卷在內容、形式、類型等方面力求反映出民族風格和文化底蘊。比如,《長詩·雲南卷(一)》編選了彝、白、哈尼、傣、壯、苗、傈僳、拉祜、納西、瑤、藏、基諾等12個民族的30部反映婚姻愛情的敘事長詩,這些作品大多採集於20世紀五六十年代,演唱者多為少數民族歌手和民間藝人,並且首次將《宛納帕麗》《南波冠》《葫蘆信》校正為傣族“三大愛情悲劇”;《傳說·吉林卷(一)》中的180餘篇作品,均取自原始採集的資料,在文本規範上進行了重新梳理並增加註釋,盡可能地還原吉林地方文化特色和民間韻味,其中的人蔘傳說、漁獵傳說、淘金傳說和木幫傳說等都是吉林省的特色文化。

  《大系》文庫既有精緻的傳統紙媒產品,也在書中以二維碼的形式鏈接相關民間文學音視頻,拓展了紙質書的內容維度,從而演示活態傳承樣本。比如,在《史詩·黑龍江卷·伊瑪堪分卷》中,讀者可以通過視頻欣賞赫哲語說唱,了解被聯合國教科文組織列為“急需保護的非物質文化遺產名錄”的赫哲族古代部落時期關於征戰、遷徙、社會、生活等英雄史詩;《小戲·湖南卷·影戲分卷》收錄了“儀式性”“非儀式性”劇本及“混合本”135個,建立了視頻資料庫,以最大程度保留和還原各區縣小戲的地方韻味,並通過地域腔調延續歷史文脈。

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

  續存民族文化的集體記憶

  盛世修典是我國自古以來的文化傳統,從《詩經》到《樂府》,從《史記》到《四庫全書》,都為中華民族文化的延續作出了獨特貢獻。編纂出版民間文學大系的意義在於續存民族文化的集體記憶,傳承民族發展的文化基因,並努力實現從立檔存志、強基固本到實現中華優秀傳統文化創造性轉化、創新性發展的銜接和提升,進而築牢中華文化共同體。

  此次出版的《大系》“神話卷”,透過“神話中國”的視角展現了中華文明的構成;“史詩卷”中代代相傳的民族史詩,蘊含珍貴的集體記憶,也是民族語言的歷史文本;“傳說卷”里豐富的民間敘事,包含民間的價值理想、生活哲學;“故事卷”里的民間故事在變化的語境中呈現了歷史經驗、文化律動中永恆不變的存在;“歌謠卷”展現了生活之歌、自然之歌,是精神情感的記錄,也是中華民族語言的瑰寶;“長詩卷”在深入發掘和打撈的過程中萃取經典,體現了長詩佳作的魅力;“說唱卷”作品中樸實的語言、真摯的情感、鮮明的個性,展示了說唱文學演繹故事、塑造典型、表達心靈乃至揭示人性的力量;“小戲卷”讓人們進一步認識和體會民間小戲的審美品格、文化價值;“諺語卷”是文學樣式、文化現象的綜合呈現,短小精悍且充滿了生產生活的智慧;“謎語卷”全面展示了跟謎語相關的文化景觀,許多資料難得而又珍貴;“俗語卷”的作品反映民俗生活,具有地方風情,是對民間口頭語言的發掘梳理和研究;“理論卷”是21世紀以來我國民間文學界第一次對最新的理論研究成果進行大規模收集、整理、編纂、回顧。總之,口耳相傳的民間文學既是民族文化的活化石,又是一部發展中的民族生活史、文化史、思想史,聯繫着民族文化的源頭並指向廣闊的未來。

  文學總體上分為兩種:一種是個人用文字創作的,以書面傳播的文學;另一種是民間集體口頭創作的,口口相傳的民間文學。後者是前者的源頭,是根性的文學。中國民間文學大系,強調文學的民間性,反映的是中國社會生活的面貌。從某種程度上說,民間文學大系就是我們民間生活的百科全書,包含民俗學、歷史學、藝術學等學科內容,蘊含豐富的史料細節,可以為民族學、民俗學的研究提供基礎性資料和基本理論,可以作為人文社會研究的基礎文獻,也可以作為教材的資料基礎,有助於生動傳承民族文化,增強中華民族的文化認同感和凝聚力。

  民間文學研究整理的總動員

  中國民間文學大系出版工程是在中國民間文藝家協會70年文獻積累的基礎上實施的。中國民協的前身是成立於1950年的中國民間文學研究會,70年來民間文學一直是其關注重點。新中國成立以來,中國民協(包括其前身“民研會”)開展了3次大規模的民間文學搶救性調查、收集、整理工作,這包括1957年的民歌調查運動、20世紀80年代的中國民間文學“三套集成”(《中國民間故事集成》《中國歌謠集成》《中國諺語集成》)普查編纂工作和始自2002年的中國民間文化遺產搶救工程。

  在數十年採集整理民間文學資料的基礎上,中國民協組織實施中國民間文學大系出版工程,進一步對“中國口頭文學遺產数字化工程”数字化搶救和整理的11000餘冊、約18億字資料進行研究、整理和編纂,並補充和完善新世紀以來的民間文學作品。

  中國民間文學大系出版工程啟動之初,我們便成立了“大系出版工程”學術委員會、編纂出版工作委員會及12個編輯專家組,以把握民間文學的實質,尊重民間文學的規律,保障編纂出版的質量和水平。工程的實施以中國民協為主,同時各級民協上下聯動,充分調動高等院校、科研院所及有關部門和機構參与的积極性,團結全國各地近千名專家學者參与編纂,凝聚了一批民間文學的專家學者和愛好者,培養了一批有能力有擔當的民間文學梯隊人才。

  大系出版工程從啟動伊始就確立了“示範帶動”的方法。一方面,在具有突出優勢的省區市部署共計55個示範卷的編纂任務;另一方面,形成了《〈中國民間文學大系〉編纂工作規範及實施辦法》《中國民間文學大系授權書》《中國民間文學大系出版工程編纂出版工作流程和相關職責》《中國民間文學大系辦公室工作分工》《中國民間文學大系出版工程相關簡稱使用規範》等系統的工作規程,以保證各項工作科學規範開展。

  《大系》編纂過程中嚴守學術規範,尊重民間文化的發展規律,關注民間文學的“活態性、生活性、歷史性和文化性”,注重大系的全面性、代表性、真實性。同時,我們還不間斷地開展研討,舉辦培訓講座,僅2018年8月以來,就在各省區市召開示範卷編纂工作啟動會、座談會、研討會20餘次,保證了《大系》內容的學術性、專業性。

  經過近三年的辛勤工作,大系出版工程取得了顯著成果。截至2019年12月17日,全國共有134卷啟動了編纂工作,其中12個示範卷已經面世,還有34卷已進入審稿、修改階段,1卷已進入出版社編校環節,其餘卷本正在補充和修改。根據規劃,大系出版工程將在2025年前出版《中國民間文學大系》大型文庫,建成电子文獻數據庫,同時開發一批經典讀本、實用讀本、普及讀本和對外宣傳推介產品和衍生產品。

  (作者:潘魯生,系中國文聯副主席、中國民間文藝家協會主席、中國民間文學大系出版工程編纂出版工作委員會主任)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

國外用戶抱怨 AirPods Max 長時間使用後,耳罩裡面會出現水滴_包裝設計

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

AirPods Max 台灣要等到明年 2 月才會到貨,但國外第一批購買者早在 12/15 就已經拿到,離現在也過了半個月,果然不出所料,新產品多少都有些問題,最近就有多位用戶在 Reddit 論壇上反應,他的 AirPods Max 使用一段時間後,耳罩內會出現水滴,雖然目前還沒有引起任何故障,但畢竟 AirPods Max 不防水,還是會讓人擔心這個現象。

國外用戶抱怨 AirPods Max 長時間使用後,耳罩裡面會出現水滴

近日一名 Donald_Filimon 網友在 Reddit 論壇上抱怨,長時間使用 AirPods Max 之後,耳罩裡面都會有多個凝結小水滴。他沒有在潮濕的環境使用過,主要都是坐在辦公桌前,如果不是某次耳機檢測不正常,他也不知道裡面發生這種情況:

從他 Twitter 推文圖片可以看到,這凝結小水滴數量還真的不是普通的多,感覺很像流汗:

So, uhh… my AirPods Max form condensation after extended use. They’ve never been used in any humid environment. The water gets inside the drivers and has caused ear detection problems. I’ve been wearing them inside sitting at a desk mainly, nothing crazy. Super concerning issue pic.twitter.com/0pWicvxLv9

— Donald Filimon (@donaldfilimon) December 27, 2020

隨後陸續有多位用戶也回報發生同樣狀況像這位 Natural-Peak-4366,他住在佛羅里達州的布雷登頓(Bradenton),使用 AirPods max 長達 2.5 個小時,想清潔耳將耳罩取下後,才注意到裡面有凝結小水滴,他覺得很奇怪,因為他沒有四處走動。這狀況真的嚇到我了:

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

好消息是,至少這幾位用戶把水滴弄乾之後,AirPods Max 並沒有出現任何故障問題,依舊可以正常使用。

不過不確定這是個案,還是每一個 AirPods Max 都有這狀況,但至少 AirPods Max 的耳罩還蠻容易拆卸的,手邊已經有的人,建議每使用一段時間就打開來擦拭一下,來避免這些凝結小水滴產生更大的問題。

至於導致水滴的原因,有人猜測可能是最近冬天比較冷,房間溫度低,長時間使用才會發生這狀況。

資料來源:Reddit

日本網友異想天開使用民間散熱法 + 散熱片幫 MacBook 散熱,結果螢幕不小心蓋上整個毀了

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

被纏上了,小王問我怎麼在 Spring Boot 中使用 JDBC 連接 MySQL_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

上次幫小王入了 Spring Boot 的門后,他覺得我這個人和藹可親、平易近人,於是隔天小王又微信我說:“二哥,快教教我,怎麼在 Spring Boot 項目中使用 JDBC 連接 MySQL 啊?”

收到問題的時候,我有點頭大,難道以後就要被小王纏上了?

沒等我發牢騷,小王就緊接着說:“二哥,你先別生氣,上次你幫了我的忙后,我在心裏感激了你一晚上,想着第一次遇到這麼親切的大佬,一定要抱緊大腿。。。。。”

馬屁拍到這份上,我的氣自然也就消了。隨後,我花了五分鐘的時間幫他解決了苦惱,沒成想,他又發給我了一個小紅包,表示對我的感謝。並建議我再寫一篇文章出來,因為他覺得像他這樣的小白還有很多。沒辦法,我關上門,開了燈,開始了今天這篇文章的創作。

01、初始化 MySQL 數據庫

既然要連接 MySQL,那麼就需要先在電腦上安裝 MySQL 服務(本文暫且跳過),並且創建數據庫和表。

CREATE DATABASE `springbootdemo`;
DROP TABLE IF EXISTS `mysql_datasource`;
CREATE TABLE `mysql_datasource` (
  `id` varchar(64NOT NULL,
  PRIMARY KEY (`id`)
ENGINE=InnoDB DEFAULT CHARSET=utf8;

02、使用 Spring Initlallzr 創建 Spring Boot 項目

創建一個 Spring Boot 項目非常簡單,通過 Spring Initlallzr(https://start.spring.io/)就可以了。

勾選 Lombok、Web、MySQL Driver、Actuator、JDBC 等五個依賴。

1)Lombok 是一種 Java 實用工具,可用來幫助開發人員消除 Java 的一些冗餘代碼,比如說可以通過註解生成 getter/setter。使用之前需要先在 IDE 中安裝插件。

2)Web 表明該項目是一個 Web 項目,便於我們直接通過 URL 來實操。

3)MySQL Driver:連接 MySQL 服務器的驅動器。

4)Actuator 是 Spring Boot 提供的對應用系統的自省和監控的集成功能,可以查看應用配置的詳細信息,例如自動化配置信息、創建的 Spring beans 以及一些環境屬性等。

5)JDBC:本篇文章我們通過 JDBC 來連接和操作數據庫。

選項選擇完后,就可以點擊【Generate】按鈕生成一個初始化的 Spring Boot 項目了。生成的是一個壓縮包,導入到 IDE 的時候需要先解壓。

03、編輯 application.properties 文件

項目導入成功后,等待 Maven 下載依賴,完成后編輯 application.properties 文件,配置 MySQL 數據源信息。

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springbootdemo
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

1)spring.datasource. 為固定格式。

2)URL 為 MySQL 的連接地址。

3)username 為數據庫的訪問用戶名。

4)password 為數據庫的訪問密碼。

5)driver-class-name 用來指定數據庫的驅動器。也可以不指定,Spring Boot 會根據 URL(有 mysql 關鍵字) 自動匹配驅動器。

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

04、編輯 Spring Boot 項目

為了便於我們操作,我們對 SpringBootMysqlApplication 類進行編輯,增加以下內容。

@SpringBootApplication
@RestController
public class SpringBootMysqlApplication {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping("insert")
    public String insert() {
        String id = UUID.randomUUID().toString();
        String sql = "insert into mysql_datasource (id,name) values ('"+id+"','沉默王二')";
        jdbcTemplate.execute(sql);
        return "插入完畢";
    }

}

1)@SpringBootApplication、@RestController、@RequestMapping 註解在[之前的文章]()中已經介紹過了,這裏不再贅述。

2)@Autowired:顧名思義,用於自動裝配 Java Bean。

3)JdbcTemplate:Spring 對數據庫的操作在 jdbc 上做了深層次的封裝,利用 Spring 的注入功能可以把 DataSource 註冊到 JdbcTemplate 之中。JdbcTemplate 提供了四個常用的方法。

①、execute() 方法:用於執行任何 SQL 語句。

②、update() 方法:用於執行新增、修改、刪除等 SQL 語句。

③、query() 方法:用於執行查詢相關 SQL 語句。

④、call() 方法:用於執行存儲過程、函數相關 SQL 語句。

本例中我們使用 execute() 方法向 mysql_datasource 表中插入一行數據 {id:uuid, name:'沉默王二'}

05、運行 Spring Boot 項目

接下來,我們直接運行 SpringBootMysqlApplication 類,這樣一個 Spring Boot 項目就啟動成功了。

這時候,我們可以直接瀏覽器的 URL 中鍵入 http://localhost:8080/insert 測試 MySQL 的插入語句是否執行成功。很遺憾,竟然出錯了。

該怎麼辦呢?這需要我們在連接字符串中顯式指定時區,修改 spring.datasource.url 為以下內容。

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springbootdemo?serverTimezone=UTC

重新運行該項目后再次訪問,發現數據插入成功了。

為了確保數據是否真的插入成功了,我們通過 Navicat(一款強大的數據庫管理和設計工具)來查看一下。

情況不妙,中文亂碼了。該怎麼辦呢?需要我們在連接字符串中顯式指定字符集,修改 spring.datasource.url 為以下內容。

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springbootdemo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC

重新運行該項目后再次訪問,發現中文不再亂碼了。

快給自己點個贊。

06、鳴謝

我是沉默王二,一枚有趣的程序員。如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀,回復【666】更有我為你精心準備的 500G 高清教學視頻(已分門別類)。

本文 GitHub 已經收錄,有大廠面試完整考點,歡迎 Star。

原創不易,莫要白票,請你為本文點個贊吧,這將是我寫作更多優質文章的最強動力。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

這些Java8官方挖過的坑,你踩過幾個?_網頁設計公司

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

導讀:系統啟動異常日誌竟然被JDK吞噬無法定位?同樣的加密方法,竟然出現部分數據解密失敗?往List裏面添加數據竟然提示不支持?日期明明間隔1年卻輸出1天,難不成這是天上人間?1582年神秘消失的10天JDK能否識別?Stream很高大上,List轉Map卻全失敗……這些JDK8官方挖的坑,你踩過幾個? 關注公眾號【碼大叔】,實戰踩坑硬核分享,一起交流!

@

目錄

  • 一、Base64:你是我解不開的迷
  • 二、被吞噬的異常:我不敢說出你的名字
  • 三、日期計算:我想留住時間,讓1天像1年那麼長
  • 四、List:一如你我初見,不增不減
  • 五、Stream處理:給你,獨一無二
  • 六、結尾:紙上得來終覺淺,絕知此事要躬行!
  • 推薦閱讀

一、Base64:你是我解不開的迷

出於用戶隱私信息保護的目的,系統上需將姓名、身份證、手機號等敏感信息進行加密存儲,很自然選擇了AES算法,外面又套了一層Base64,之前用的是sun.misc.BASE64Decoder/BASE64Encoder,網上的資料基本也都是這種寫法,運行得很完美。但這種寫法在idea或者maven編譯時就會有一些黃色告警提示。到了Java 8后,Base64編碼已經成為Java類庫的標準,內置了 Base64 編碼的編碼器和解碼器。於是乎,我手賤地修改了代碼,改用了jdk8自帶的Base64方法

import java.util.Base64;

public class Base64Utils {

    public static final Base64.Decoder DECODER = Base64.getDecoder();
    public static final Base64.Encoder ENCODER = Base64.getDecoder();

    public static String encodeToString(byte[] textByte) {
        return ENCODER.encodeToString(textByte);
    }

    public static byte[] decode(String str) {
        return DECODER.decode(str);
    }

}

程序員的職業操守咱還是有的,構造新老數據、自測、通過,提交測試版本。信心滿滿,我要繼續延續我 0 Bug的神話!然後……然後版本就被打回了。

Caused by: java.lang.IllegalArgumentException: Illegal base64 character 3f
    at java.util.Base64$Decoder.decode0(Base64.java:714)
    at java.util.Base64$Decoder.decode(Base64.java:526)
    at java.util.Base64$Decoder.decode(Base64.java:549)

關鍵是這個錯還很詭異,部分數據是可以解密的,部分解不開

Base64依賴於簡單的編碼和解碼算法,使用65個字符的US-ASCII子集,其中前64個字符中的每一個都映射到等效的6位二進制序列,第65個字符(=)用於將Base64編碼的文本填充到整數大小。後來產生了3個變種:

  • RFC 4648:Basic
    此變體使用RFC 4648和RFC 2045的Base64字母表進行編碼和解碼。編碼器將編碼的輸出流視為一行; 沒有輸出行分隔符。解碼器拒絕包含Base64字母表之外的字符的編碼。​
  • RFC 2045:MIME
    此變體使用RFC 2045提供的Base64字母表進行編碼和解碼。編碼的輸出流被組織成不超過76個字符的行; 每行(最後一行除外)通過行分隔符與下一行分隔。解碼期間將忽略Base64字母表中未找到的所有行分隔符或其他字符。
  • RFC 4648:Url
    此變體使用RFC 4648中提供的Base64字母表進行編碼和解碼。字母表與前面显示的字母相同,只是-替換+和_替換/。不輸出行分隔符。解碼器拒絕包含Base64字母表之外的字符的編碼。
S.N. 方法名稱 & 描述
1 static Base64.Decoder getDecoder()
返回Base64.Decoder解碼使用基本型base64編碼方案。
2 static Base64.Encoder getEncoder()
返回Base64.Encoder編碼使用的基本型base64編碼方案。
3 static Base64.Decoder getMimeDecoder()
返回Base64.Decoder解碼使用MIME類型的base64解碼方案。
4 static Base64.Encoder getMimeEncoder()
返回Base64.Encoder編碼使用MIME類型base64編碼方案。
5 static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)
返回Base64.Encoder編碼使用指定的行長度和線分隔的MIME類型base64編碼方案。
6 static Base64.Decoder getUrlDecoder()
返回Base64.Decoder解碼使用URL和文件名安全型base64編碼方案。
7 static Base64.Encoder getUrlEncoder()
返回Base64.Decoder解碼使用URL和文件名安全型base64編碼方案。

關於base64用法的詳細說明,可參考:https://juejin.im/post/5c99b2976fb9a070e76376cc

對於上面的錯誤,網上有的說法是,建議使用Base64.getMimeDecoder()Base64.getMimeEncoder(),對此我只能建議:老的系統如果已經有數據了,就不要使用jdk自帶的Base64了。JDK官方的Base64和sun的base64是不兼容的!不要替換!不要替換!不要替換!

二、被吞噬的異常:我不敢說出你的名字

這個問題理解起來還是蠻費腦子的,所以我把這個系統異常發生的過程提煉成了一個美好的故事,放鬆一下,吟詩一首!

最怕相思濃
一切皆是你
唯獨
不敢說出你的名字
— 碼大叔

這個問題是在使用springboot的註解時遇到的問題,發現JDK在解析註解時,若註解依賴的類定義在JVM加載時不存在,也就是NoClassDefFoundError時,實際拿到的異常將會是ArrayStoreException,而不是NoClassDefFoundError,涉及到的JDK里的類是AnnotationParser.java, 具體代碼如下:

private static Object parseClassArray(int paramInt, ByteBuffer paramByteBuffer, ConstantPool paramConstantPool, Class<?> paramClass) {
    Class[] arrayOfClass = new Class[paramInt];
    int i = 0;
    int j = 0;
    for (int k = 0; k < paramInt; k++){
        j = paramByteBuffer.get();
        if (j == 99) {
            // 注意這個方法
        	arrayOfClass[k] = parseClassValue(paramByteBuffer, paramConstantPool, paramClass);
        } else {
        	skipMemberValue(j, paramByteBuffer);
        	i = 1;
        }
    }
    return i != 0 ? exceptionProxy(j) : arrayOfClass;
}
private static Object parseClassValue(ByteBuffer paramByteBuffer, ConstantPool paramConstantPool, Class<?> paramClass) {
    int i = paramByteBuffer.getShort() & 0xFFFF;
    try
    {
        String str = paramConstantPool.getUTF8At(i);
        return parseSig(str, paramClass);
    } catch (IllegalArgumentException localIllegalArgumentException) {
        return paramConstantPool.getClassAt(i);
    } catch (NoClassDefFoundError localNoClassDefFoundError) {
         // 注意這裏,異常發生了轉化
        return new TypeNotPresentExceptionProxy("[unknown]", localNoClassDefFoundError);
    } catch (TypeNotPresentException localTypeNotPresentException) {
        return new TypeNotPresentExceptionProxy(localTypeNotPresentException.typeName(), localTypeNotPresentException.getCause());
    }
}

parseClassArray這個方法中,預期parseClassValue返回Class對象,但看實際parseClassValue的邏輯,在遇到NoClassDefFoundError時,返回的是TypeNotPresentExceptionProxy,由於類型強轉失敗,最終拋出的是java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy,此時只能通過debug到這行代碼,找到具體是缺少哪個類定義,才能解決這個問題。

筆者重現一下發現這個坑的場景,有三個module,module3依賴module2但未聲明依賴module1,module2依賴module1,但聲明的是optional類型,依賴關係圖如下:

上面每個module中有一個Class,我們命名為ClassInModuleX。ClassInModule3啟動時在註解中使用了ClassInModule2的類,而ClassInModule2這個類的繼承了ClassInModule1,這幾個類的依賴關係圖如下:

如此,其實很容易知道在module運行ClassInModule3時,會出現ClassInModule1的NoClassDefFoundError的,但實際運行時,你能看到的異常將不是NoClassDefFoundError,而是java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy,此時,若想要知道具體是何許異常,需通過debug在AnnotationParser中定位具體問題,以下展示兩個截圖,分別對應系統控制台實際拋出的異常和通過debug發現的異常信息。

控制台異常信息:

注意異常實際在紅色圈圈這裏,自動收縮了,需要展開才可以看到通過debug發現的異常信息:

如果你想體驗這個示例,可關注公眾號碼大叔和筆者交流。如果你下次遇到莫名的java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy,請記得用這個方法定位具體問題。

三、日期計算:我想留住時間,讓1天像1年那麼長

Java8之前日期時間操作相當地麻煩,無論是Calendar還是SimpleDateFormat都讓你覺得這個設計怎麼如此地反人類,甚至還會出現多線程安全的問題,阿里巴巴開發手冊中就曾禁用static修飾SimpleDateFormat。好在千呼萬喚之後,使出來了,Java8帶來了全新的日期和時間API,還帶來了Period和Duration用於時間日期計算的兩個API。

Duraction和Period,都表示一段時間的間隔,Duraction正常用來表示時、分、秒甚至納秒之間的時間間隔,Period正常用於年、月、日之間的時間間隔。

網上的大部分文章也是這麼描述的,於是計算兩個日期間隔可以寫成下面這樣的代碼:

// parseToDate方法作用是將String轉為LocalDate,略。
LocalDate date1 = parseToDate("2020-05-12");
LocalDate date2 = parseToDate("2021-05-13");
// 計算日期間隔
int period = Period.between(date1,date2).getDays();

一個是2020年,一個是2021年,你認為間隔是多少?1年?
恭喜你,和我一起跳進坑裡了(畫外音:裏面的都擠一擠,動一動,又來新人了)。
正確答案應該是:1天。

這個單詞的含義以及這個方法看起來確實是蠻誤導人的,一不注意就會掉進坑裡。Period其實只能計算同月的天數、同年的月數,不能計算跨月的天數以及跨年的月數。

正確寫法1

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

 long period = date2.toEpochDay()-date1.toEpochDay();

toEpochDay():將日期轉換成Epoch 天,也就是相對於1970-01-01(ISO)開始的天數,和時間戳是一個道理,時間戳是秒數。顯然,該方法是有一定的局限性的

正確寫法2

long period = date1.until(date2,ChronoUnit.DAYS);

使用這個寫法,一定要注意一下date1和date2前後順序:date1 until date2。

正確做法3(推薦)

 long period = ChronoUnit.DAYS.between(date1, date2);

ChronoUnit:一組標準的日期時間單位。這組單元提供基於單元的訪問來操縱日期,時間或日期時間。 這些單元適用於多個日曆系統。這是一個最終的、不可變的和線程安全的枚舉。

看到”適用於多個日曆系統“這句話,我一下子想起來歷史上1582年神秘消失的10天,在JDK8上是什麼效果呢?1582-10-15和1582-10-04你覺得會相隔幾天呢?11天還是1天?有興趣的小夥伴自己去寫個代碼試試吧。

打開你的手機,跳轉到1582年10月,你就能看到這消失的10天了。

四、List:一如你我初見,不增不減

這個問題其實在JDK里存在很多年了,JDK8中依然存在,也是很多人最容易跳的一個坑!直接上代碼:

public List<String> allUser() {
    // 省略
    List<String> currentUserList = getUser();
    currentUserList.add("碼大叔");
    // 省略
}

就是上面這樣一段代碼,往一個list里添加一條數據,你覺得結果是什麼呢?“碼大叔”成功地添加到了List里?天真,不報個錯你怎麼能意識到JDK存在呢。

Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.add(AbstractList.java:148)

原因
因為在getUser方法里,返回的List使用的是Arrays.asList生成的,示例:

    private List<String> getUser(){
        return Arrays.asList("劍聖","小九九");
    }

我們來看看Arrays.asList的源碼

    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
 private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
   		private final E[] a;
        // 部分代碼略
        ArrayList(E[] array) {
            // 返回的是一個定長的數組
            a = Objects.requireNonNull(array);
        }
        // 部分代碼略
   }

很明顯,返回的實際是一個定長的數組,所以只能“一如你我初見”,初始化什麼樣子就什麼樣子,不能新增,不能減少。如果你理解了,那我們就再來一個栗子

   int[] intArr  = {1,2,3,4,5};
   Integer[] integerArr  = {1,2,3,4,5};
   String[] strArr = {"1", "2", "3", "4", "5"};
   List list1 = Arrays.asList(intArr);
   List list2 = Arrays.asList(integerArr);
   List list3 = Arrays.asList(strArr);
   System.out.println("list1中的數量是:" + list1.size());
   System.out.println("list2中的數量是:" + list2.size());
   System.out.println("list3中的數量是:" + list3.size());

你覺得答案是什麼?預想3秒鐘,揭曉答案,看跟你預想的是否一致呢?

list1中的數量是:1
list2中的數量是:5
list3中的數量是:5

是不是和你預想又不一樣了?還是回到Arrays.asList方法,該方法的輸入只能是一個泛型變長參數。基本類型是不能泛型化的,也就是說8個基本類型不能作為泛型參數,要想作為泛型參數就必須使用其所對應的包裝類型,那前面的例子傳遞了一個int類型的數組,為何程序沒有報編譯錯誤呢?在Java中,數組是一個對象,它是可以泛型化的,也就是說我們的例子是把一個int類型的數組作為了T的類型,所以在轉換后在List中就只有1個類型為int數組的元素了。除了int,其它7個基本類型的數組也存在相似的問題。

JDK里還為我們提供了一個便捷的集合操作工具類Collections,比如多個List合併時,可以使用Collections.addAll(list1,list2), 在使用時也同樣要時刻提醒自己:“請勿踩坑”!

五、Stream處理:給你,獨一無二

Java8中新增了Stream流 ,通過流我們能夠對集合中的每個元素進行一系列并行或串行的流水線操作。當使用一個流的時候,通常包括三個基本步驟:獲取一個數據源(source)→ 數據轉換→執行操作獲取想要的結 果,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以 像鏈條一樣排列,變成一個管道。

項目上千萬不要使用Stream,因為一旦用起來你會覺得真屏蔽詞爽,根本停不下來。當然不可避免的,還是有一些小坑的。

假設我們分析用戶的訪問日誌,放到list里。

list.add(new User("碼大叔", "登錄公眾號"));
list.add(new User("碼大叔", "編寫文章"));

因為一些原因,我們要講list轉為map,Steam走起來,

private static void convert2MapByStream(List<User> list) {
    Map<String, String> map = list.stream().collect(Collectors.toMap(User::getName, User::getValue));
    System.out.println(map);
}

咣當,掉坑裡了,程序將拋出異常:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 碼大叔

使用Collectors.toMap() 方法中時,默認key值是不允許重複的。當然,該方法還提供了第三個參數:也就是出現 duplicate key的時候的處理方案

如果在開發的時候就考慮到了key可能重複,你需要在這樣定義convert2MapByStream方法,聲明在遇到重複key時是使用新值還是原有值:

    private static void convert2MapByStream(List<User> list) {
        Map<String, String> map = list.stream().collect(Collectors.toMap(User::getName, User::getValue, (oldVal, newVal) -> newVal));
        System.out.println(map);
    }

關於Stream的坑其實還是蠻多的,比如尋找list中的某個對象,可以使用findAny().get(),你以為是找到就返回找不到就就返回null?依然天真,找不到會拋出異常的,需要使用額外的orElse方法。

六、結尾:紙上得來終覺淺,絕知此事要躬行!

所謂JDK官方的坑,基本上都是因為我們對技術點了解的不夠深入,望文生義,以為是怎樣怎樣的,而實際上我們的自以為是讓我們掉進了一個又一個坑裡。面對着這些坑,我流下了學藝不精的眼淚!但也有些坑,確實發生的莫名其妙,比如吞噬異常,沒有理解JDK為什麼這麼設計。還有些坑,誤導性確實太強了,比如日期計算、list操作等。最後只能說一句:

紙上得來終覺淺,絕知此事要躬行!
編碼不易,且行且珍惜!

推薦閱讀

Try-Catch包裹的代碼異常后,竟然導致了產線事務回滾!
Redis 6.0 新特性-多線程連環13問!
報告老闆,微服務高可用神器已祭出,您花巨資營銷的高流量來了沒?
我成功攻擊了Tomcat服務器,大佬們的反應亮了

公眾號:碼大叔
資深程序員、架構師技術社區
微服務 | 大數據 | 架構設計 | 技術管理
個人微信:itmadashu

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

年畫“圈粉”路在何方?_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

“如果年畫進行創新,你會喜歡什麼樣的年畫?為什麼會喜歡它?”

“我覺得你一定不是年輕人。”“現在的年輕人多喜歡‘傳統文化’。”……

這是一次有意思的採訪。當記者對遠在廣東佛山的一位“90后”孫璐璐拋出這樣的問題時,就被“呼之欲出”的代溝感震得“花枝凌亂”。自認為通過工作接觸,已經潛入“00后”的審美世界,而又得以在眾“90后”同事的熏陶中了解了年輕人的審美趣味,但這次的採訪才讓記者發覺,過往一切認知都只是皮毛。

孫璐璐說:“現在的年輕人多喜歡‘傳統文化’,覺得李寧酷斃了,漢服超級美,大白兔奶糖的香水、六神花露水的雞尾酒就是流行元素符號……”

從一番激動的言辭中,記者依稀看到當下年輕人對傳統文化那份堅定的認同。這些年,漢服熱、懷舊復古風等潮流風行,讓越來越多的人重新感受到傳統的別樣韻味。傳統文化復蘇、文化自信增強,在這樣的大背景下,作為與傳統文化息息相關的非遺,應該抓住時機,重新綻放新顏。比如,曾在春節年俗中不可或缺的年畫,如何在保留美好文化內涵的前提下,另闢蹊徑,重振春節傳統年俗?

嫁接新平台興起來

在年畫的發展歷史中,也曾起起落落。年畫內容不斷更迭、不斷充盈,跟進時代的步伐。1949年後,年畫曾迎來小陽春。那時,許多專業畫家加入年畫的創作隊伍,打破了舊年畫的一些固定模式,大膽借鑒其他畫種的表現手法,使年畫的面貌煥然一新。

時代在變,人們的審美觀念在變。傳統文化雖好,卻不能囿於過去的框架止步不前,在萬象更新的時代進程中,傳統文化需要年輕化的表達。基於這樣的理念,清華大學等高校與傳承人結對子,走出了一條年畫與時代接軌的新路子。

2019年,在文化和旅遊部非物質文化遺產司的指導與支持下,由傳統工藝與材料研究文化和旅遊部重點實驗室(清華大學)主辦,清華大學美術學院視覺傳達設計系和繪畫系共同承辦的清華大學年畫日新創作營,精選了天津楊柳青、蘇州桃花塢、山東高密、山東楊家埠、陝西鳳翔、河北武強等11個代表性年畫產地的年畫人與設計師、美術創作者組成團隊,在10周的時間里創作出作品48件(套),後期成果將投入商業渠道。

創作營的成果首先在北京國際設計周上展示發布,經過3個月的不懈努力,最終落地京東年貨節。佛山、開封、楊柳青、武強、綿竹、灘頭、高密七大年畫項目帶着濃郁的地域特色,將祥和喜慶的產品帶進電商平台。除了藉助京東非遺頻道銷售年畫和年畫衍生品,“萌萌噠”的年神當起了京東數碼產品的“導購”,一些年畫項目還與戴爾、Kindle、華為等知名品牌深度合作,推出了富有中國風味的春節禮盒。

清華大學美術學院副教授、藝術史論系主任陳岸瑛說,這次嘗試的成功說明古老的年畫並非註定落伍於時代,而是具有強大的發展潛力,特別是在新媒體時代,只要把年畫中的美好寓意挖掘出來,與人民群眾對美好生活的嚮往結合起來,年畫就不愁沒有粉絲。

清華大學年畫日新創作營營員石彥敏創作的楊柳青年畫《連年有餘》

助力老手藝活起來

有一件事曾讓楊柳青木版年畫國家級代表性傳承人霍慶有很有感觸,2015年他在天津圖書館舉辦個人年畫藝術展,來看展覽的人不少,但普遍是中老年人,幾乎沒有年輕人,更別提小朋友了。“這給我一個提示,要想讓年畫藝術傳承發展下去,就應該思考如何把孩子們吸引過來。讓年畫走到孩子身邊,陪伴下一代成長。”霍慶有說。

年畫的遠離,帶來的是年味的變淡,不僅是孩子、年輕人對它越來越陌生,曾經充盈民間的紅火年味也不知所蹤。在過去,過年貼年畫,圖的是喜慶吉利,蘊含的是祈福祝願。在那個穿新衣戴新帽、鞭炮聲聲的年節里,我們用一張張年畫集結普天下的喜慶顏色,把一段叫作“年”的時光裝扮起來。如今,物質生活越來越豐富,傳統的年俗年味卻越來越淡了。

時代的車輪滾滾向前,現代印刷術的出現,令傳統印刷的木版年畫受到了衝擊,有的地區年畫一度失傳。2002年,還是一位生意人的張榮強,因緣際會接到了數十年來投身文化遺產搶救的文化學者馮驥才的電話。那時,馮驥才想去尋找四川夾江年畫,卻發現已經沒有可以製作年畫的師傅了。一直酷愛美術的他,決定將精力投入到拯救夾江年畫上。

2010年,他拜夾江年畫老字號作坊“董大興榮”傳人董貴中為師,希望盡最大努力讓這項傳統文化繼續傳承下去。但這條路走起來比想象中更難。起初,擺在張榮強面前的難題是幾乎沒有可利用的資源,沒有夾江年畫製作流程的文字記載,也沒有會實際操作的師傅,甚至連照片都寥寥無幾。此後,張榮強走訪了大量民間手藝人,並不斷尋找相關資料,經過反覆推敲、多次修改,終於成功恢復了夾江年畫的木版套色印刷技藝,並復刻出《鯉魚跳龍門》《福祿宮》《榮華富貴》《財源湧進》《陳姑趕潘》等近30張經典年畫作品。

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

福美祥作坊創作的灘頭年畫《鼠慶豐年》

轉換新語境潮起來

中國人民大學美學與現代藝術研究所研究員張成源在研究東方美學與年畫的關聯時發現,年畫蘊含的東方美學內涵非常豐富,它包含祈禱、健康、豐收、忠孝等。而這些寓意是古今相通的,留存於每一代國人的思想中。因此,年畫重回春節、重回現代生活無需刻意,只要將這一內涵重新挖掘出來,轉換新的話語形式,年畫依然會受到人們的青睞。

在新一代年畫傳承人中,年畫女俠kk的網名似乎比她劉鍾萍的本名更具傳播效應。她當旅遊講解員時與佛山木版年畫結緣。原先的師兄師姐都各尋出路去了,給師父打下手的劉鍾萍卻留了下來。師父馮炳棠是佛山唯一一位掌握木版年畫全套工序的老藝術家,“唯一”二字讓劉鍾萍在不知不覺中對這份工作有了擔當。師父故去后,她挑起了傳承佛山木版年畫的重任。

年畫需要傳承,也需要創新。為了將傳統年畫與現代需求嫁接,她把傳統技藝與現代文化相結合,創作出“諸神復活”系列年畫,受到了市場的歡迎,而傳統年畫與新潮包裝的結合,也迅速俘獲了年輕人的心。起初有人來求購姻緣年畫,受到啟發,她將喜神和合二仙賦予“脫單神器”的新闡釋,頓時使傳統年畫穿越時空與現代接軌。之後,“一個億小目標的財神”“考神狀元及第逢考必過”“天姬送子兒女雙全的二胎神器”“新房入夥鎮宅神器紫薇大神”等系列相繼面世。

劉鍾萍說:“過去的傳統年畫很難吸引到年輕人,我就讓年畫跟他們的生活發生聯繫,他們帶着願望而來,我就負責把他們的願望通過年畫的寓意傳達出去。”

章昉創作的綿竹年畫《老鼠嫁女》

清華大學美術學院教授唐薇創作的佛山年畫《子開鴻蒙》

傳統文化並不缺市場,缺少的是我們重新打扮它的用心。一個鄉村女孩李子柒,將恬靜、本真的鄉村生活用鏡頭表現出來,在互聯網上贏得了國內外網友廣泛的共鳴。因此,承載着人們對美好未來憧憬和嚮往的年畫也可以迎來複興,只要更多力量聚集於此,並賦予年畫一些創新發展的思路和內容,年畫“圈粉”時機來了。

傳統文化並不缺市場,缺少的是我們重新打扮它的用心。一個鄉村女孩李子柒,將恬靜、本真的鄉村生活用鏡頭表現出來,在互聯網上贏得了國內外網友廣泛的共鳴。因此,承載着人們對美好未來憧憬和嚮往的年畫也可以迎來複興,只要更多力量聚集於此,並賦予年畫一些創新發展的思路和內容,年畫“圈粉”時機來了。

專家觀點

中國人民大學美學與現代藝術研究所研究員 張成源

科技的日新月異不會停下前進的腳步,傳統文化的熏陶也應注重其形式。我們要將其中的倫理道德、家風家訓、行為底線、人格境界提煉出來,用新的形式讓它重新被人們接受。在現代社會中,不能強求年畫的量化,因為傳統技藝無法規模生產,但是年畫衍生品可以。年畫也許是小眾文化,年畫衍生品卻可以是大眾文化。因此,當我們在極力調動眾多力量加入年畫的創新中時,欠缺的不僅是年畫衍生品的創作人才,更重要是的消費人群對它的認識。

中國傳媒大學文化產業管理學院副教授 楊 紅

自2019年開始,圍繞“年畫重回春節”這一主題,非遺傳承人與各類互聯網企業、文化創意團隊合作,開始藉助新媒體開發年畫拼圖微信小遊戲、年畫體驗類H5、年畫賀歲動漫、年畫音頻故事課、年畫微信表情包等多種形態的年畫主題創新應用,傳統年畫成為了春節里熱門的網絡傳播內容。

這些嘗試說明,儘管不少與節日相關的非物質文化遺產在當代的應用場景逐漸消失,但它們卻可以藉助網絡與創意,與我們再次相遇,攜帶着不可或缺的文化意義重回節日場景。在這個過程中,給傳統文化適當“減負”,為文化普及適當“減壓”,讓年畫等越來越多傳統IP在當代實現全民認知與共享,是新時代弘揚中華優秀傳統文化的重要路徑。(杜潔芳 王學思)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

漂洋過海中國年,共繪文明交流對話絢麗景緻_網頁設計公司

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

隨着春節的臨近, “歡樂春節”系列活動在世界各地紅紅火火開展起來,年味十足,洋溢五洲。近年來,隨着中外人文交流走深走實,以春節為代表的中國節慶文化在世界上越來越深入人心,中華文化影響力持續擴大。

在聯袂演出中促進文化交流互鑒,在互動體驗中領略非遺魅力,在美食中愛上中華飲食文化,在展覽中欣賞中國藝術,在商貿中探索文化和旅遊發展的合作路徑……自2010年以來,“歡樂春節”系列活動不斷創新形式、豐富內容,已成為用文化語言講好中國故事、彰顯文化自信的重要文化品牌。“歡樂春節”的子品牌,如“行走的年夜飯”“藝術中國匯”“新春音樂會”“春節廟會”等也深受各國民眾歡迎。越來越多的海外民眾用漢語表達新春祝福,很多國家和地區持續多年舉辦“歡樂春節”系列活動,“歡樂春節”的受眾正在擴大。

越來越多的社會力量參与“歡樂春節”系列活動的組織開展,讓世界看到了更立體的中國。在文旅融合深入推進的當下,作為其中重要着力點的交流融合正在“歡樂春節”中得到彰顯。各國開展的特色活動從內容到形式上雖有差異,卻都以更加開放、自信的心態,傳播歡樂、和諧、對話、共享和共建人類命運共同體的價值理念,文化中國、美麗中國得以推介。

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

人文交流是中外民眾友好交往的橋樑。深化人文交流互鑒是消除隔閡誤解、促進民心相知相通的重要途徑,增進中外文化交流互鑒有利於夯實構建人類命運共同體的人文基礎。文化交流是多層次、雙向的,在推介中華優秀傳統文化的同時,也應該把外國的優秀文化成果請進來,實現良性互動。文化影響力是國家綜合國力的體現,增強中華文化的吸引力、增進中外人文交流,需要挖掘更多傳統文化資源,打造更多品牌。

習近平主席在亞洲文明對話大會開幕式主旨演講中指出:“交流互鑒是文明發展的本質要求。只有同其他文明交流互鑒、取長補短,才能保持旺盛生命活力。”

打破人文交流的壁壘,徜徉在不同文明的長河,啟發自己、豐富別人是激發文化創新活力的有效路徑。豐富和創新人文交流的內容與形式,既需要重要時間節點的濃墨重彩,也需要日常生活的潤物無聲。中華文化走出去需要聆聽世界的聲音,以更宏大的視野、更多元的視角、更豐富的表現手法、更具表現力的話語,探尋推進中外人文交流的突破口,共繪文明交流對話絢麗景緻。(黨雲峰)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

通過一個vue+elementUI的小實例來講解一下它們是如何使用的_包裝設計

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

  需求:點擊一個按鈕,彈出一個模態框,這個模態框有兩個tab,tab中是各種報警條件,這些報警條件是從數據庫中動態取出的,數據庫中數據變更后,這個界面也要變更,我們可以查看和編輯這些報警條件。底部“確定”按鈕點擊的時候,會同時將這兩個tab中的內容都保存到數據庫中去,數據錄入要驗證輸入的格式。

  對於熟練的人來說,實現其實很簡單,但是對於沒有經驗的人來說,如果按照官網給的那些簡單實例來做,你會發現,出現一些奇怪的問題,諸如,文本框不能編輯內容,表單驗證無效等。感覺elementUI官網的示例太過於粗淺,無法適應實際工作中的許多需求場景。

  界面效果如下圖所示:

  分析:用到的組件:el-dialog、el-tabs

   AlarmSet.vue代碼:

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

<template>
  <div>
    <div class="alarm-set" v-loading="winLoading">
      <el-tabs v-model="activeName" type="border-card" v-if="alarmTmplData.length>0">
        <el-tab-pane label="製冷站" name="coldSet">
          <ColdSet
            ref="coldSet"
            :alarmTmplData="alarmTmplData"
            @onSubmit="coldSetSummit"
            :showAlarmSetWin.sync="showAlarmSetWin"
          ></ColdSet>
        </el-tab-pane>
        <el-tab-pane label="末端" name="endSet">
          <EndSet
            ref="endSet"
            :alarmTmplData="alarmTmplData"
            @onSubmit="endSetSummit"
            :showAlarmSetWin.sync="showAlarmSetWin"
          ></EndSet>
        </el-tab-pane>
      </el-tabs>
    </div>
    <div slot="footer" class="dialog-footer">
      <div style="display: inline-block">
        <el-button type="primary" @click="onSubmit" :loading="btnLoading">確 定</el-button>
        <el-button @click="isHide">取 消</el-button>
      </div>
    </div>
  </div>
</template>

<script>
import ColdSet from "./ColdSet";
import EndSet from "./EndSet";
import mixinsOption from "@/mixins/mixin-options";
import { alarmService } from "@/services/cold-station-service";
// import { alarmConditionData } from "@/mock/json.js";
export default {
  mixins: [mixinsOption],
  props: {
    showAlarmSetWin: {
      type: Boolean,
      default: false
    },
    alarmTmpl: {
      type: Object,
      default: {}
    }
  },
  components: {
    ColdSet,
    EndSet
  },
  data() {
    return {
      alarmConditionData: [], //報警條件數據
      activeName: "coldSet",
      alarmTmplData: [],
      coldConditionData: [], //冷站報警條件數據
      endConditionData: [], //末端報警條件數據
      isColdValid: false,
      isEndValid: false,
      btnLoading: false
    };
  },
  watch: {
    showAlarmSetWin: {
      handler(val) {
        console.log("showAlarmSetWin", val);
        if (val) {
          this.initData();
        }
      },
      immediate: true
    }
  },
  methods: {
    //初始化數據
    initData() {
      this.$store.commit("base/setWinLoading", true);
      console.log("initData");
      alarmService
        .getConditionList({
          groupNumber: this.groupNumber,
          projectNumber: this.projectNumber
        })
        .then(res => {
          if (res.code === 200) {
            this.alarmConditionData = res.data;
            this.createAlarmTmplData(res.data);
          }
          this.$store.commit("base/setWinLoading", false);
        });
    },
    //構造報警模板數據
    createAlarmTmplData(conditionData) {
      let res = [];
      this.alarmTmplData = this.alarmTmpl.data;
      if (this.alarmTmpl) {
        this.alarmTmplData.forEach(n => {
          // debugger;
          n.descr = eval(n.descr);
          let item = conditionData.find(m => m.tempId == n.id);
          if (item) {
            n["alarmLevel"] = item.alarmLevel;
            n["suggestion"] = item.suggestion;
            n["firstVal"] = item.firstVal;
            n["secondVal"] = item.secondVal;
            n["fourthVal"] = item.fourthVal;
            n["thirdVal"] = item.thirdVal;
            n["status"] = item.status;
          }
        });
      }
      // console.log("this.alarmTmplData :>> ", this.alarmTmplData);
    },
    //確定操作
    onSubmit() {
      this.$refs["coldSet"].onSubmit();
      this.$refs["endSet"].onSubmit();
      if (this.isColdValid && this.isEndValid) {
        this.btnLoading = true;
        let list = this.coldConditionData
          .concat(this.endConditionData)
          .map(m => {
            return {
              tempId: m.id,
              alarmLevel: m.alarmLevel,
              firstVal: m.firstVal,
              secondVal: m.secondVal,
              thirdVal: m.thirdVal,
              fourthVal: m.fourthVal,
              status: m.status,
              suggestion: m.suggestion
            };
          });

        alarmService
          .batchEdit({
            projectNumber: this.projectNumber,
            groupNumber: this.groupNumber,
            list: list
          })
          .then(res => {
            if (res.code === 200) {
              this.$message({
                message: "操作成功!",
                type: "success",
                duration: this.$baseConfig.messageDuration
              });
            }
            this.btnLoading = false;
          })
          .catch(() => {
            this.btnLoading = false;
          });
      }
    },
    coldSetSummit(val, isValid) {
      if (isValid) {
        this.isColdValid = isValid;
        this.coldConditionData = val;
      }
    },
    endSetSummit(val, isValid) {
      if (isValid) {
        this.isEndValid = isValid;
        this.endConditionData = val;
      }
    },
    //取消
    isHide() {
      this.$emit("update:showAlarmSetWin", false);
    }
  }
};
</script>

<style lang="scss" scoped>
.alarm-set {
  height: 600px;
  /deep/ .el-tabs--border-card {
    height: 100%;
  }
  /deep/ .el-tabs__content {
    height: calc(100% - 60px);
  }
}
</style>

重寫elementUI組件樣式,可以使用/deep/ 進行樣式穿透覆寫。

關於表單驗證:由於是動態生成的表單,所以不能按照官網提供的示例來做。

 el-form中不指定驗證規則,而是在el-form-item中指定,如下:
:prop="`list.${rowIndex}.${fieldArr[2*index+index+1]}`" :rules="rules.numberRule"
表單數據ruleForm中要對數據進行初始化,否則會無法自動識別數據變化,建議採用深拷貝的形式
考慮到冷站和末端這兩個組件中的代碼可以復用,抽取公共js文件set-mixin.js,然後通過mixins引入,這個類似於組合,還有一種繼承是通過extends來實現。
分析界面雖然是動態的,但是總共只有幾種類型,分別是:1個文本框,2個文本框,沒有文本框。他們都帶有建議信息這一行,所以可以用幾個v-if來區分。
import { alarmLevelOptions, fieldArr } from '@/enum/alarm-enum.js';
import Regexps from '@/utils/regexp.js';
import mixinsOption from '@/mixins/mixin-options';
export default {
  props: {
    alarmTmplData: {
      type: Array,
      default: () => {
        return [];
      },
    },
    showAlarmSetWin: {
      type: Boolean,
      default: false,
    },
  },
  mixins: [mixinsOption],
  data() {
    return {
      levelOptions: alarmLevelOptions(),
      ruleForm: {
        list: [],
      },
      rules: {
        numberRule: [
          {
            pattern: Regexps.commonNumber,
            message: '僅支持3位數和帶1位小數',
            trigger: 'blur',
          },
        ],
        suggestion: [
          { max: 10, message: '長度不能超過10個字符', trigger: 'blur' },
        ],
      },
      fieldArr,
      tmplData: [],
    };
  },
  computed: {
    activeNames() {
      let activeNames = this.tmplData
        .filter((f) => f.descParamType != 0)
        .map((n) => {
          return n.id;
        });
      console.log('activeNames :>> ', activeNames);
      return activeNames;
    },
  },
  methods: {
    initData(type) {
      console.log('initData', type);
      if (this.alarmTmplData.length > 0) {
        this.tmplData = this.alarmTmplData.filter((n) => n.sys == type);
        this.ruleForm.list = JSON.parse(JSON.stringify(this.tmplData));
        // console.log('條件設置initData :>> ', this.ruleForm.list);
      }
    },
  },
};

ColdSet.vue代碼:

<template>
  <div>
    <el-form :model="ruleForm" ref="ruleForm">
      <el-collapse v-model="activeNames">
        <el-collapse-item :name="item.id" v-for="(item,rowIndex) in ruleForm.list" :key="item.id">
          <template slot="title">
            <div class="header">
              <el-checkbox v-model="item.status" :true-label="0" :false-label="1">{{item.name}}</el-checkbox>
              <el-select v-model="item.alarmLevel" size="small">
                <el-option
                  v-for="item in levelOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                ></el-option>
              </el-select>
            </div>
          </template>
          <div class="content vertical">
            <div class="row one" v-if="item.descParamType==1">
              <div class="item" v-for="(subItem,index) in item.descr" :key="index">
                <label>{{subItem}}</label>
                <el-form-item
                  label="大於"
                  :prop="`list.${rowIndex}.${fieldArr[index]}`"
                  :rules="rules.numberRule"
                >
                  <el-input v-model="item[`${fieldArr[index]}`]" size="small"></el-input>
                </el-form-item>
              </div>
            </div>
            <template v-if="item.descParamType==2">
              <div class="row two" v-for="(subItem,index) in item.descr" :key="index">
                <div class="item">
                  <label>{{subItem}}</label>
                  <el-form-item
                    label="大於"
                    :prop="`list.${rowIndex}.${fieldArr[2*index+index]}`"
                    :rules="rules.numberRule"
                  >
                    <el-input
                      v-model="item[`${fieldArr[2*index+index]}`]"
                      size="small"
                    ></el-input>
                  </el-form-item>
                  <el-form-item
                    label="或小於"
                    :prop="`list.${rowIndex}.${fieldArr[2*index+index+1]}`"
                    :rules="rules.numberRule"
                  >
                    <el-input
                      v-model="item[`${fieldArr[2*index+index+1]}`]"
                      size="small"
                    ></el-input>
                  </el-form-item>
                </div>
              </div>
            </template>

            <div class="row one">
              <el-form-item
                label="建議信息"
                :prop="`list.${rowIndex}.suggestion`"
                :rules="rules.suggestion"
              >
                <el-input
                  v-model="item.suggestion"
                  class="max-width"
                  size="small"
                  placeholder="請儘快處理"
                ></el-input>
              </el-form-item>
            </div>
          </div>
        </el-collapse-item>
      </el-collapse>
    </el-form>
  </div>
</template>

<script>
import { alarmLevelOptions, fieldArr } from "@/enum/alarm-enum.js";
import Regexps from "@/utils/regexp.js";
import { validateVal } from "@/utils/validate-utils.js";
import { alarmService } from "@/services/cold-station-service";
import setMixin from "./set-mixin";
export default {
  mixins: [setMixin],
  watch: {
    alarmTmplData: {
      handler(val) {
        console.log("showAlarmSetWin cold :>> ", val);
        if (val) {
          this.initData(1);
        }

         else{            this.$refs[“ruleForm”].resetFields();         }

      },
deep:true, immediate:
true } }, methods: { onSubmit() { console.log("冷站確定 :>> "); this.$refs["ruleForm"].validate(valid => { if (valid) { console.log("驗證成功"); this.$emit("onSubmit", this.ruleForm.list, true); } }); } } }; </script> <style lang="scss" scoped> @import "./set.scss"; </style>

EndSet.vue:

<template>
  <div>
    <el-form :model="ruleForm" ref="ruleForm">
      <el-collapse v-model="activeNames">
        <el-collapse-item :name="item.id" v-for="(item,rowIndex) in ruleForm.list" :key="item.id">
          <template slot="title">
            <div class="header">
              <el-checkbox v-model="item.status" :true-label="0" :false-label="1">{{item.name}}</el-checkbox>
              <el-select v-model="item.alarmLevel" size="small">
                <el-option
                  v-for="item in levelOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                ></el-option>
              </el-select>
            </div>
          </template>
          <div class="content vertical">
            <div class="row two" v-if="item.descParamType==1">
              <div class="item" v-for="(subItem,index) in item.descr" :key="index">
                <label>{{subItem}}</label>
                <el-form-item
                  label="大於"
                  :prop="`list.${rowIndex}.${fieldArr[index]}`"
                  :rules="rules.numberRule"
                  v-if="index==0"
                >
                  <el-input v-model="item[fieldArr[index]]" size="small"></el-input>
                </el-form-item>
              </div>
            </div>
            <div class="row two" v-if="item.descParamType==2">
              <div class="item" v-for="(subItem,index) in item.descr" :key="index">
                <label>{{subItem}}</label>
                <el-form-item
                  label="大於"
                  :prop="`list.${rowIndex}.${fieldArr[2*index+index]}`"
                  :rules="rules.numberRule"
                >
                  <el-input v-model="item[fieldArr[2*index+index]]" size="small"></el-input>
                </el-form-item>
                <el-form-item
                  label="或小於"
                  :prop="`list.${rowIndex}.${fieldArr[2*index+index+1]}`"
                  :rules="rules.numberRule"
                >
                  <el-input v-model="item[fieldArr[2*index+index+1]]" size="small"></el-input>
                </el-form-item>
              </div>
            </div>
            <div class="row two" v-if="item.descParamType==3">
              <div class="item">
                <el-form-item
                  :label="subItem"
                  :prop="`list.${rowIndex}.${fieldArr[index]}`"
                  :rules="rules.numberRule"
                  v-for="(subItem,index) in item.descr"
                  :key="index"
                >
                  <el-input v-model="item[fieldArr[index]]" size="small"></el-input>
                </el-form-item>
              </div>
            </div>
            <template v-if="item.descParamType==4">
              <div class="row one" v-for="(subItem,index) in item.descr" :key="index">
                <div class="item">{{subItem}}</div>
                <div class="item">
                  <el-form-item
                    :label="index==0?'小於':'大於'"
                    :prop="`list.${rowIndex}.${fieldArr[index]}`"
                    :rules="rules.numberRule"
                  >
                    <el-input v-model="item[fieldArr[index]]" size="small"></el-input>
                  </el-form-item>
                </div>
              </div>
            </template>
            <!-- <div class="row multi-row" v-if="item.descParamType==4">
              multi-item       
            </div>-->
            <div class="row one">
              <el-form-item
                label="建議信息"
                :prop="`list.${rowIndex}.suggestion`"
                :rules="rules.suggestion"
              >
                <el-input
                  v-model="item.suggestion"
                  class="max-width"
                  size="small"
                  placeholder="請儘快處理"
                ></el-input>
              </el-form-item>
            </div>
          </div>
        </el-collapse-item>
      </el-collapse>
    </el-form>
  </div>
</template>

<script>
import setMixin from "./set-mixin";
import { alarmService } from "@/services/cold-station-service";
export default {
  mixins: [setMixin],
  watch: {
    alarmTmplData: {
      handler(val) {
        console.log("showAlarmSetWin end:>> ", val);
        if (val) {
          this.initData(2);
        }

         else{            this.$refs[“ruleForm”].resetFields();         }

      },
deep:true, immediate:
true } }, methods: { onSubmit() { console.log("末端確定 :>> "); this.$refs["ruleForm"].validate(valid => { if (valid) { console.log("驗證成功"); this.$emit("onSubmit", this.ruleForm.list, true); } }); } } }; </script> <style lang="scss" scoped> @import "./set.scss"; </style>

由於要兩個tab中都驗證通過時,才提交表單,所以兩個表單都要驗證,只有都驗證通過時在提交表單,提交表單之前,要合併表單數據再統一提交。父組件調用子組件的方法 this.$refs[“coldSet”].onSubmit();

考慮到彈窗組件的性能問題,我們可以通過將显示標識以 :showAlarmSetWin.sync=”showAlarmSetWin”這樣的形式傳遞給子組件,(注意,要加sync關鍵字)子組件監聽showAlarmSetWin,當彈窗显示時,加載數據並初始化,並設置屬性:immediate: true,讓彈窗第一次執行時也加載數據。

當窗體隱藏時,重置表單:
this.$refs[“ruleForm”].resetFields();
為了防止同一時間多次點擊操作按鈕“確定”,可以給按鈕加上loading
數據列表格式:

{
    "data": [{
        "id": 246,
        "tempId": "1",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 247,
        "tempId": "2",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 248,
        "tempId": "3",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 249,
        "tempId": "4",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 250,
        "tempId": "5",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 251,
        "tempId": "6",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 252,
        "tempId": "7",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 253,
        "tempId": "8",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 254,
        "tempId": "9",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 255,
        "tempId": "10",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 256,
        "tempId": "11",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 257,
        "tempId": "12",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 258,
        "tempId": "13",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 259,
        "tempId": "14",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 260,
        "tempId": "15",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 261,
        "tempId": "16",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 262,
        "tempId": "50",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 263,
        "tempId": "51",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 264,
        "tempId": "52",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 265,
        "tempId": "53",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 266,
        "tempId": "54",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 267,
        "tempId": "55",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 268,
        "tempId": "56",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 269,
        "tempId": "57",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 270,
        "tempId": "58",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 271,
        "tempId": "59",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 272,
        "tempId": "60",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 273,
        "tempId": "61",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 274,
        "tempId": "62",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 275,
        "tempId": "63",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 276,
        "tempId": "64",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 277,
        "tempId": "65",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 278,
        "tempId": "66",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 279,
        "tempId": "67",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 280,
        "tempId": "68",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 281,
        "tempId": "69",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 282,
        "tempId": "101",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 283,
        "tempId": "102",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 84.0,
        "secondVal": 16.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 284,
        "tempId": "103",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 59.0,
        "secondVal": 49.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 285,
        "tempId": "104",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 34.0,
        "secondVal": 22.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 286,
        "tempId": "105",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 92.0,
        "secondVal": 66.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 287,
        "tempId": "106",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 55.0,
        "secondVal": 29.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 288,
        "tempId": "107",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 86.0,
        "secondVal": 81.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 289,
        "tempId": "108",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 67.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 290,
        "tempId": "109",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 85.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 291,
        "tempId": "110",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 16.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 292,
        "tempId": "111",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 92.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 293,
        "tempId": "112",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": "請儘快安排人檢查",
        "firstVal": 10.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 294,
        "tempId": "113",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 44.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 295,
        "tempId": "114",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": "請儘快安排人檢查",
        "firstVal": 61.0,
        "secondVal": 35.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 296,
        "tempId": "115",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 48.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 297,
        "tempId": "116",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 298,
        "tempId": "117",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 299,
        "tempId": "118",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 76.0,
        "secondVal": 23.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 300,
        "tempId": "119",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 64.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 301,
        "tempId": "120",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 302,
        "tempId": "121",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 73.0,
        "secondVal": 15.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 303,
        "tempId": "122",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 90.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 304,
        "tempId": "201",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 305,
        "tempId": "202",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 306,
        "tempId": "203",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 307,
        "tempId": "204",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 32.0,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 308,
        "tempId": "205",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 309,
        "tempId": "206",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 310,
        "tempId": "207",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 311,
        "tempId": "208",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 312,
        "tempId": "209",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 313,
        "tempId": "210",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 314,
        "tempId": "211",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 315,
        "tempId": "212",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 316,
        "tempId": "213",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 317,
        "tempId": "214",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 318,
        "tempId": "215",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 319,
        "tempId": "216",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 320,
        "tempId": "217",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 321,
        "tempId": "218",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 322,
        "tempId": "219",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 323,
        "tempId": "220",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 36.0,
        "secondVal": 11.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 324,
        "tempId": "221",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": 66.0,
        "secondVal": 59.0,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }, {
        "id": 325,
        "tempId": "222",
        "projectNumber": "440300A088",
        "groupNumber": "G1",
        "alarmLevel": 1,
        "suggestion": null,
        "firstVal": null,
        "secondVal": null,
        "thirdVal": null,
        "fourthVal": null,
        "status": 0
    }],
    "code": 200,
    "msg": "成功",
    "errors": null
}

View Code

模板數據列表格式:

{
    "data": [{
        "id": "1",
        "name": "制冷機啟動失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"制冷機組設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "10",
        "name": "冷凍泵故障",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷凍水泵設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "101",
        "name": "冷凍水供水溫度不在合理範圍",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷凍水供水溫度\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "102",
        "name": "冷卻塔出水溫度溫度不在合理範圍",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷卻水回水溫度-室外濕球溫度(℃)\",\"或當室外濕球溫度<17℃時,冷卻水回水溫度設定值(℃)\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "103",
        "name": "冷凍供回水溫差(壓差)不在合理範圍",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷凍水溫差(℃)\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "104",
        "name": "冷卻供回水溫差不在合理範圍",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷卻水溫差(℃)\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "105",
        "name": "電流負載限制",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷水機組電流負載率\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "106",
        "name": "冷凝溫度限制",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷凝器出水溫度(℃)\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "107",
        "name": "蒸發溫度限制",
        "sys": 1,
        "type": 3,
        "descr": "[\"蒸發器出水溫度(℃)\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "108",
        "name": "蒸發器內水壓力超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"蒸發器冷媒壓力(Mpa)\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "109",
        "name": "蒸發器內水流量超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷凍水流量/當前制冷機的額定流量*製冷劑運行台數\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "11",
        "name": "冷卻泵啟動失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷卻水泵設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "110",
        "name": "冷凝器內水壓力超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷機冷凝器冷媒壓力(Mpa)\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "111",
        "name": "冷凝器內水流量超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷卻水流量/當前制冷機的額定流量*制冷機運行台數\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "112",
        "name": "冷凍水側小溫差過高",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,蒸發器出水溫度-冷媒溫度(℃)\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "113",
        "name": "冷卻水側小溫差過高",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷凝器冷媒溫度-出水溫度(℃)\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "114",
        "name": "冷凍水流量超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷凍泵流量/額定流量\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "115",
        "name": "冷凍泵功率超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷凍泵功率/額定功率\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "116",
        "name": "冷凍泵頻率超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷凍泵運行頻率不在配置的上下限範圍內\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "117",
        "name": "冷凍水溫差超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷凍水溫差在(3,7)範圍內\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "118",
        "name": "冷卻泵流量超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"冷卻泵流量/額定流量\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "119",
        "name": "冷卻泵功率超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷卻泵功率/額定功率\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "12",
        "name": "冷卻泵停止失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷卻水泵設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "120",
        "name": "冷卻塔頻率超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷卻塔頻率不在配置的上下限範圍\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "121",
        "name": "冷卻回水溫度超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷卻回水溫度\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "122",
        "name": "冷卻塔功率超限",
        "sys": 1,
        "type": 3,
        "descr": "[\"正常運行時段,冷卻塔功率/額定功率\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "13",
        "name": "冷卻泵故障",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷卻水泵設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "14",
        "name": "冷卻塔啟動失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷卻塔風機故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "15",
        "name": "冷卻塔停止失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷卻塔風機故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "16",
        "name": "冷卻塔故障",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷卻塔風機故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "2",
        "name": "制冷機停止失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"制冷機組設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "201",
        "name": "室內溫度傳感器需要校核",
        "sys": 2,
        "type": 4,
        "descr": "[\"每日6點,當前末端溫度反饋值與其他末端溫度反饋值相差2℃以上\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "202",
        "name": "工作時間前過早開啟空調末端",
        "sys": 2,
        "type": 2,
        "descr": "[\"工作時間前過早開啟空調末端\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "203",
        "name": "室外溫度高而新風閥未關閉",
        "sys": 2,
        "type": 2,
        "descr": "[\"室外溫度高而新風閥未關閉\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "204",
        "name": "室外溫度低而新風閥未開啟",
        "sys": 2,
        "type": 2,
        "descr": "[\"非營業時間,風櫃設定溫度-室外濕球溫度(℃)\",\"新風閥未開啟\"]",
        "descParamType": 1,
        "hasSuge": 1,
        "runTime": 3
    }, {
        "id": "205",
        "name": "空調風機變頻器自動控制未啟用",
        "sys": 2,
        "type": 2,
        "descr": "[\"當前時間<關機時間,室外濕球溫度>24且風櫃溫度人工設定\",\"或室外濕球溫度在[5,24]且風櫃溫度人工設定\",\"或室外濕球溫度<5且風櫃(風盤)溫度人工設定\"]",
        "descParamType": 4,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "206",
        "name": "室內CO2過高",
        "sys": 2,
        "type": 3,
        "descr": "[\"室內CO2(PPM)\",\"持續時間(min)\"]",
        "descParamType": 3,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "207",
        "name": "空調末端風機故障",
        "sys": 2,
        "type": 1,
        "descr": "[\"空調末端風機故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "208",
        "name": "空調末端新風閥故障",
        "sys": 2,
        "type": 1,
        "descr": "[\"空調末端新風閥故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "209",
        "name": "空調末端迴風閥故障",
        "sys": 2,
        "type": 1,
        "descr": "[\"空調末端迴風閥故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "210",
        "name": "空調末端水閥故障",
        "sys": 2,
        "type": 1,
        "descr": "[\"空調末端水閥故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "211",
        "name": "風櫃開啟失敗",
        "sys": 2,
        "type": 1,
        "descr": "[\"風櫃開啟失敗\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "212",
        "name": "風櫃停止失敗",
        "sys": 2,
        "type": 1,
        "descr": "[\"風櫃停止失敗\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "213",
        "name": "風盤開啟失敗",
        "sys": 2,
        "type": 1,
        "descr": "[\"風盤開啟失敗\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "214",
        "name": "風盤停止失敗",
        "sys": 2,
        "type": 1,
        "descr": "[\"風盤停止失敗\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "215",
        "name": "時序配置異常",
        "sys": 2,
        "type": 2,
        "descr": "[\"時序配置異常\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "216",
        "name": "末端設備為手動模式",
        "sys": 2,
        "type": 2,
        "descr": "[\"末端設備為手動模式\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "217",
        "name": "風櫃故障報警",
        "sys": 2,
        "type": 1,
        "descr": "[\"風櫃故障報警\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "218",
        "name": "風盤故障報警",
        "sys": 2,
        "type": 1,
        "descr": "[\"風盤故障報警\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "219",
        "name": "風櫃頻率超限",
        "sys": 2,
        "type": 3,
        "descr": "[\"風櫃頻率超限\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "220",
        "name": "室內溫度不在合理範圍",
        "sys": 2,
        "type": 3,
        "descr": "[\"風櫃迴風溫度(℃)\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "221",
        "name": "末端水力不平衡",
        "sys": 2,
        "type": 2,
        "descr": "[\"營業時間內,風櫃迴風溫度(℃)\"]",
        "descParamType": 2,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "222",
        "name": "風櫃頻率超限",
        "sys": 2,
        "type": 3,
        "descr": "[\"風櫃頻率CAB_FRQ不在配置的上下限範圍\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 2
    }, {
        "id": "3",
        "name": "制冷機故障",
        "sys": 1,
        "type": 1,
        "descr": "[\"制冷機組設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "4",
        "name": "制冷機冷凍側閥門啟動失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"制冷機組設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "5",
        "name": "制冷機冷凍側閥門停止失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"制冷機組設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "50",
        "name": "水力不平衡",
        "sys": 1,
        "type": 2,
        "descr": "[\"觸發冰爽模式\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "51",
        "name": "非變頻自動運行",
        "sys": 1,
        "type": 2,
        "descr": "[\"設備工頻啟停或頻率手動設定\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "52",
        "name": "全自動或一鍵啟動失敗",
        "sys": 1,
        "type": 2,
        "descr": "[\"全自動模式、一鍵啟動模式無法生效\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "53",
        "name": "過早開製冷站設備",
        "sys": 1,
        "type": 2,
        "descr": "[\"營業時間前過早開製冷站設備\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "54",
        "name": "停業后未及時停止製冷站設備",
        "sys": 1,
        "type": 2,
        "descr": "[\"停業后未及時停止製冷站設備\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "55",
        "name": "低溫未啟用全新風策略",
        "sys": 1,
        "type": 2,
        "descr": "[\"天氣溫度低而製冷站運行\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "56",
        "name": "制冷機加減載不合理",
        "sys": 1,
        "type": 2,
        "descr": "[\"手動模式下,執行制冷機的加減載\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "57",
        "name": "制冷機加減載失敗",
        "sys": 1,
        "type": 2,
        "descr": "[\"制冷機加減載失敗\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "58",
        "name": "冷水機組冷凍水旁通",
        "sys": 1,
        "type": 2,
        "descr": "[\"正常運行時段,運行狀態-停止狀態的冷機蒸發器進水溫度<1\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "59",
        "name": "冷水機組冷卻水旁通",
        "sys": 1,
        "type": 2,
        "descr": "[\"正常運行時段,運行狀態-停止狀態的冷機冷凝器進水溫度<1\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "6",
        "name": "制冷機冷卻側閥門啟動失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"制冷機組設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "60",
        "name": "制冷機頻繁啟停機",
        "sys": 1,
        "type": 2,
        "descr": "[\"設備啟停記錄中,同一設備的啟動和停止時間間隔<30min\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "61",
        "name": "冷卻泵頻繁啟停機",
        "sys": 1,
        "type": 2,
        "descr": "[\"設備啟停記錄中,同一設備的啟動和停止時間間隔<30min\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "62",
        "name": "冷凍泵頻繁啟停機",
        "sys": 1,
        "type": 2,
        "descr": "[\"設備啟停記錄中,同一設備的啟動和停止時間間隔<30min\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "63",
        "name": "冷卻塔頻繁啟停機",
        "sys": 1,
        "type": 2,
        "descr": "[\"設備啟停記錄中,同一設備的啟動和停止時間間隔<30min\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "64",
        "name": "未啟用控制模式",
        "sys": 1,
        "type": 2,
        "descr": "[\"冷凍泵運行頻率為手動設定或工頻運行\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "65",
        "name": "冷卻塔集水盤溢流",
        "sys": 1,
        "type": 2,
        "descr": "[\"冷卻塔液位COT1_WL>後台配置中的液位上限\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "66",
        "name": "冷卻塔集水盤乾涸",
        "sys": 1,
        "type": 2,
        "descr": "[\"冷卻塔液位COT1_WL<後台配置中的液位下限\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "67",
        "name": "冷卻塔啟停機不及時",
        "sys": 1,
        "type": 2,
        "descr": "[\"冷卻塔在主機停機后5min以外關閉\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "68",
        "name": "冷卻塔未啟用控制模式",
        "sys": 1,
        "type": 2,
        "descr": "[\"冷卻塔運行頻率為手動設定或工頻運行\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "69",
        "name": "冷卻泵啟停機不及時",
        "sys": 1,
        "type": 2,
        "descr": "[\"冷卻泵在主機啟動前2min以外啟動,或冷卻泵在主機停機后5min以外關閉\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 1
    }, {
        "id": "7",
        "name": "制冷機冷卻側閥門停止失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"制冷機組設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "8",
        "name": "冷凍泵啟動失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷凍水泵設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }, {
        "id": "9",
        "name": "冷凍泵停止失敗",
        "sys": 1,
        "type": 1,
        "descr": "[\"冷凍水泵設備故障\"]",
        "descParamType": 0,
        "hasSuge": 1,
        "runTime": 0
    }],
    "code": 200,
    "msg": "成功",
    "errors": null
}

View Code

總共兩個接口,一個加載模板,一個加載數據,模板數據只第一次進入到頁面的時候調用,數據是每次打開彈窗都要調用。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務