@airhang/vue-book-reader 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"vue-book-reader.common.js","mappings":";;UAAA;UACA;;;;;WCDA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA,E;;;;;WCPA,8CAA8C,yD;;;;;WCA9C;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D,E;;;;;WCNA,2B;;;;;;;;;;;;;;;;ACAA;AACA;;AAEA;AACA;AACA,MAAM,KAAuC,EAAE;AAAA,yBAQ5C;;AAEH;AACA;AACA,IAAI,qBAAuB;AAC3B;AACA;;AAEA;AACA,kDAAe,IAAI;;;ACtBnB,+BAA+B,6BAA6B,iBAAiB,6CAA6C,oBAAoB,cAAc,cAAc,eAAe,MAAM,eAAe,MAAM,MAAM,iLAAiL,GAAG,MAAK,EAAE,CAAgE,uCAAuC,mCAAmC,oEAAoE,uDAAuD,8BAA8B,uDAAuD,uDAAuD,2HAA2H,6OAA6O,KAAK,sEAAsE,sBAAsB,4BAA4B,iCAAiC,EAAE,kBAAkB,yBAAyB,uCAAuC,+HAA+H,sHAAsH,YAAY,8DAA8D,yCAAyC,iBAAiB,2CAA2C,qDAAqD,mBAAmB,sCAAsC,qBAAqB,YAAY,qCAAqC,sBAAsB,2CAA2C,yHAAyH,mBAAmB,OAAO,oCAAoC,YAAY,sDAAsD,6CAA6C,qCAAqC,+CAA+C,YAAY,oCAAoC,kEAAkE,iIAAiI,mCAAmC,YAAY,+BAA+B,yCAAyC,iBAAiB,2EAA2E,mBAAmB,uCAAuC,qBAAqB,YAAY,sCAAsC,sBAAsB,2CAA2C,yHAAyH,YAAY,iCAAiC,mBAAmB,OAAO,uBAAuB,YAAY,2EAA2E,6CAA6C,qCAAqC,+CAA+C,YAAY,oCAAoC,qEAAqE,uCAAuC,yCAAyC,qCAAqC,2CAA2C,YAAY,oCAAoC,yEAAyE,yHAAyH,YAAY,yBAAyB,yBAAyB,2CAA2C,gCAAgC,gDAAgD,iBAAiB;AAC3pI;AACA;AACA;AACA,aAAa,yCAAyC,YAAY,2BAA2B,uBAAuB,gCAAgC,yBAAyB,YAAY,+BAA+B,gCAAgC,+BAA+B,0EAA0E,2CAA2C,+BAA+B,0GAA0G,KAAK,yBAAyB,4BAA4B,eAAe,+BAA+B,sBAAsB,iCAAiC,6BAA6B,2rBAA2rB,+BAA+B,uBAAuB,6DAA6D,yCAAyC,4CAA4C,eAAe,oCAAoC,gCAAgC,aAAa,6BAA6B,gDAAgD,6BAA6B,yEAAyE,6BAA6B,uCAAuC,iBAAiB,oDAAoD,sDAAsD,KAAK,yBAAyB,wCAAwC,aAAa,kCAAkC,yCAAyC,mCAAmC,iCAAiC,mCAAmC;AAC3yE;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK,MAAM,6KAA6K,4BAA4B,eAAe,oCAAoC,2BAA2B,aAAa,6BAA6B,+BAA+B,gCAAgC,8BAA8B,YAAY,wBAAwB,uFAAuF,iCAAiC,uBAAuB,+CAA+C,2BAA2B,eAAe,8BAA8B,gCAAgC,KAAK,qBAAqB,6BAA6B,8BAA8B,8BAA8B,KAAK,oBAAoB,mDAAmD,8BAA8B,6DAA6D,8BAA8B,YAAY,4BAA4B,YAAY,yBAAyB,yHAAyH,4BAA4B,4BAA4B,aAAa,yBAAyB,yGAAyG,oCAAoC,+BAA+B,4DAA4D,OAAO,8EAA8E,KAAK,6HAA6H,QAAQ,yDAAyD,4BAA4B,mCAAmC;AAC94D;AACA,mCAAmC,6BAA6B,iBAAiB,gCAAgC,YAAY,8BAA8B,YAAY,2BAA2B,YAAY,2BAA2B,YAAY,2BAA2B,cAAc,2BAA2B,4EAA4E,+BAA+B,YAAY,2BAA2B;AAC3c,CAAC;;;;;;AEdD;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,mCAAmC;AACpE,iCAAiC,uBAAuB;;AAExD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,kBAAkB,QAAQ;AAC1B;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,kBAAkB,OAAO;AACzB;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA,SAAS,oBAAQ;AACjB;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEoO;;;ACnRpO;AACA;AACA;AACA;AACA;AACA;;AAEsG;;AAEtG;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,MAAM;AACN,eAAe,QAAQ;AACvB;AACA;;AAEA;AACA;AACA;;AAEA;AACA,oBAAoB,QAAQ;AAC5B;;AAEA;AACA,yBAAyB,WAAW;AACpC;;AAEA;AACA;AACA;;AAEA;AACA,yBAAyB,SAAS;AAClC;;AAEA;AACA,yBAAyB,WAAW;AACpC;;AAEA;AACA,yBAAyB,OAAO;AAChC;;AAEA;AACA,WAAW,oBAAQ;AACnB;;AAEA;AACA,CAAC;;AAED;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,OAAO;AACP;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,KAAK;AACL,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,YAAY;AACZ;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA,UAAU;AACV;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,gCAAgC;AAC5E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,OAAO;AACP,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,eAAe;AACf;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,eAAe;AACf;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA,QAAQ;;AAER;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;;AAER;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA,4EAA4E,uCAAuC;AACnH,gCAAgC;AAChC;AACA;AACA,6FAA6F,sCAAsC;AACnI;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,SAAS;AACT,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,SAAS;AACT,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0FAA0F;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA,oDAAoD,4BAA4B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA,kBAAkB;AAClB;AACA,kBAAkB;AAClB;AACA;AACA;;AAEA;AACA;;AAEA;AACA,kCAAkC,aAAa,0BAA0B,wBAAwB;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,wBAAwB;AAC7B;AACA;AACA,KAAK,UAAU,wDAAwD,MAAM,wPAAwP,YAAY,0CAA0C,wCAAwC,EAAE,YAAY,0CAA0C,8CAA8C,MAAM,sBAAsB,wBAAwB,2CAA2C,+CAA+C,MAAM,uBAAuB,wBAAwB,SAAS,+DAA+D,EAAE,+BAA+B;AACrzB;AACA;AACA;AACA;AACA,WAAW,SAAS,6CAA6C,KAAK,wBAAwB,kCAAkC,qDAAqD;AACrL;AACA;AACA;AACA;AACA,WAAW,SAAS,8CAA8C,KAAK,wBAAwB,kCAAkC,iCAAiC,SAAS,2BAA2B,EAAE;AACxM;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,sCAAsC,iBAAiB;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,EAAE,YAAY,aAAa,kFAAkF,kCAAkC,2BAA2B,EAAE,IAAI,2BAA2B;AACxN;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM,2FAA2F;AAC5G;;AAEA;AACA;AACA,mBAAmB;AACnB,kCAAkC,oCAAoC,iCAAiC,WAAW,YAAY,gCAAgC,gBAAgB,+CAA+C,gBAAgB,qCAAqC,kBAAkB,WAAW,YAAY,0BAA0B,iBAAiB,gCAAgC,kBAAkB,UAAU,YAAY,MAAM,iBAAiB,qCAAqC,OAAO,sCAAsC,QAAQ,+BAA+B,kBAAkB,iBAAiB,uBAAuB,kBAAkB,2BAA2B,0BAA0B,kBAAkB,MAAM,OAAO,4BAA4B,2BAA2B,6BAA6B,gCAAgC,sBAAsB,oCAAoC,WAAW,YAAY,qCAAqC;;AAEx8B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,MAAM,kEAAkE;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEwC;;;AC/yCxC,IAAI,mEAAM,qBAAqB,6BAA6B,iBAAiB,yCAAyC,0BAA0B,iCAAiC,yBAAyB,qBAAqB,uCAAuC,4BAA4B,UAAU,wBAAwB,EAAE,YAAY,4BAA4B,WAAW,2BAA2B,8BAA8B,+BAA+B,yBAAyB,aAAa,yBAAyB,8BAA8B,gCAAgC,cAAc,aAAa,oFAAoF,uCAAuC,oCAAoC,WAAW,4BAA4B,KAAK,0BAA0B,kCAAkC,sCAAsC,uEAAuE,eAAe,2BAA2B,8BAA8B,+BAA+B,6BAA6B,0BAA0B,gCAAgC,YAAY,8BAA8B,uDAAuD,8BAA8B,YAAY,yBAAyB,yDAAyD,+BAA+B,4BAA4B,qFAAqF,6BAA6B,iDAAiD,4BAA4B,iBAAiB,MAAM,SAAS,iBAAiB,WAAW,KAAK,8BAA8B,EAAE,eAAe,8BAA8B;AAChwD;AACA,IAAI,4EAAe;;;;;ACqEnB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,+EAAe;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,CAAC;;;ACpY6I,CAAC,0FAAe,0CAAG,EAAC,C;;ACAlK;;;;;AEAA;;AAEA;AACA;AACA;;AAEe,SAAS,sCAAkB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;AC/F6G;AACvC;AACL;AACjE,CAA2G;;;AAG3G;AACmG;AACnG,gBAAgB,sCAAU;AAC1B,EAAE,qDAAM;AACR,EAAE,mEAAM;AACR,EAAE,4EAAe;AACjB;AACA;AACA;AACA;AACA;AACA;;AAEA,wDAAe,iB;;;ACsSyB;AACmB;;AAE3D,sEAAe;AACf;AACA;AACA,YAAY;AACZ,uBAAuB;AACvB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;;AC58CoI,CAAC,iFAAe,iCAAG,EAAC,C;;ACAzJ;;;;;AEAoG;AACvC;AACL;AACxD,CAAkG;;;AAGlG;AACmG;AACnG,IAAI,oBAAS,GAAG,sCAAU;AAC1B,EAAE,4CAAM;AACR,EAAE,MAAM;AACR,EAAE,eAAe;AACjB;AACA;AACA;AACA;AACA;AACA;;AAEA,+CAAe,oBAAS,Q;;ACnB4B;AACkB;AACtE;AACA;AACA,YAAY;AACZ,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,0CAAe;AACf;AACA,YAAY;AACZ,qBAAqB;AACrB,CAAC;AACD;AAIC;;;AC7BuB;AACA;AACxB,8CAAe,KAAG;AACI","sources":["webpack://@airhang/vue-book-reader/webpack/bootstrap","webpack://@airhang/vue-book-reader/webpack/runtime/define property getters","webpack://@airhang/vue-book-reader/webpack/runtime/hasOwnProperty shorthand","webpack://@airhang/vue-book-reader/webpack/runtime/make namespace object","webpack://@airhang/vue-book-reader/webpack/runtime/publicPath","webpack://@airhang/vue-book-reader/./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?ed9e","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?f598","webpack://@airhang/vue-book-reader/./node_modules/rematrix/dist/rematrix.es.js","webpack://@airhang/vue-book-reader/./node_modules/flipbook-vue/dist/vue2/flipbook.mjs","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?ef5d","webpack://@airhang/vue-book-reader/src/components/BookCatalogueDrawer.vue","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?e206","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?4baa","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?5d6d","webpack://@airhang/vue-book-reader/./node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue","webpack://@airhang/vue-book-reader/src/components/BookReader.vue","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?0add","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?9965","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?5f27","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue","webpack://@airhang/vue-book-reader/./src/index.js","webpack://@airhang/vue-book-reader/./node_modules/@vue/cli-service/lib/commands/build/entry-lib.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.p = \"\";","/* eslint-disable no-var */\n// This file is imported into lib/wc client bundles.\n\nif (typeof window !== 'undefined') {\n var currentScript = window.document.currentScript\n if (process.env.NEED_CURRENTSCRIPT_POLYFILL) {\n var getCurrentScript = require('@soda/get-current-script')\n currentScript = getCurrentScript()\n\n // for backward compatibility, because previously we directly included the polyfill\n if (!('currentScript' in document)) {\n Object.defineProperty(document, 'currentScript', { get: getCurrentScript })\n }\n }\n\n var src = currentScript && currentScript.src.match(/(.+\\/)[^/]+\\.js(\\?.*)?$/)\n if (src) {\n __webpack_public_path__ = src[1] // eslint-disable-line\n }\n}\n\n// Indicate to webpack that this file can be concatenated\nexport default null\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"photo-album-container\",style:({ transform: `scale(${_vm.zoomScale}) translate(${_vm.translateX}px, ${_vm.translateY}px)` }),on:{\"touchstart\":_vm.onContainerTouchStart,\"touchmove\":_vm.onContainerTouchMove,\"touchend\":_vm.onContainerTouchEnd,\"dblclick\":_vm.onContainerDoubleClick,\"click\":_vm.onContentClick}},[(false)?_c('div',{staticClass:\"album-header\"},[_c('h1',[_vm._v(\"氪氪\")])]):_vm._e(),(_vm.contentReady)?_c('div',{staticClass:\"flipbook-wrapper\",on:{\"dblclick\":_vm.onFlipbookDoubleClick,\"!touchstart\":function($event){return _vm.onFlipbookTouchStart.apply(null, arguments)},\"!touchend\":function($event){return _vm.onFlipbookTouchEnd.apply(null, arguments)}}},[(_vm.flipMode === 'flip' && _vm.flag)?_c('flipbook',{ref:\"flipbook\",class:['flipbook', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized'],attrs:{\"pages\":_vm.pages,\"pagesHiRes\":_vm.pagesHiRes,\"startPage\":_vm.startPage,\"zooms\":null,\"ambient-light\":0.6,\"gloss\":0.4,\"single-page\":_vm.isMobile,\"click-to-flip\":false,\"wheel-to-flip\":!_vm.isMobile,\"swipe-to-flip\":true,\"center-pages\":true},on:{\"flip-left-end\":_vm.onFlipLeftEnd,\"flip-right-end\":_vm.onFlipRightEnd},scopedSlots:_vm._u([{key:\"default\",fn:function({ page, canFlipLeft, canFlipRight }){return undefined}}],null,false,2449366912)}):(_vm.flipMode === 'slide')?_c('div',{ref:\"slideViewer\",class:['slide-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized'],on:{\"touchstart\":_vm.onSlideViewerTouchStart,\"touchmove\":_vm.onSlideViewerTouchMove,\"touchend\":_vm.onSlideViewerTouchEnd}},[_c('div',{staticClass:\"slide-container\",style:(_vm.slideContainerStyle)},_vm._l((_vm.pages),function(page,index){return _c('div',{key:index,staticClass:\"slide-page\",class:{ 'slide-page-active': index + 1 === _vm.currentPage }},[(page)?_c('img',{staticClass:\"slide-page-image\",attrs:{\"src\":page,\"alt\":\"\"}}):_c('div',{staticClass:\"slide-page-placeholder\"},[_vm._v(\"封面/封底\")])])}),0)]):(_vm.flipMode === 'fade')?_c('div',{ref:\"fadeViewer\",class:['fade-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']},[_c('transition',{attrs:{\"name\":\"fade-page\",\"mode\":\"out-in\"}},[_c('div',{key:_vm.currentPage,staticClass:\"fade-page-container\"},[(_vm.pages[_vm.currentPage - 1])?_c('img',{staticClass:\"fade-page-image\",attrs:{\"src\":_vm.pages[_vm.currentPage - 1],\"alt\":\"\"}}):_c('div',{staticClass:\"fade-page-placeholder\"},[_vm._v(\"封面/封底\")])])])],1):(_vm.flipMode === 'scroll')?_c('div',{ref:\"scrollViewer\",class:['scroll-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized'],on:{\"scroll\":_vm.onScrollViewerScroll}},[_c('div',{staticClass:\"scroll-container\"},_vm._l((_vm.pages),function(page,index){return _c('div',{key:index,ref:'scrollPage' + index,refInFor:true,staticClass:\"scroll-page\"},[(page)?_c('img',{staticClass:\"scroll-page-image\",attrs:{\"src\":page,\"alt\":\"\"}}):_c('div',{staticClass:\"scroll-page-placeholder\"},[_vm._v(\"封面/封底\")])])}),0)]):(_vm.flipMode === 'clip')?_c('div',{ref:\"clipViewer\",class:['clip-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']},[_c('div',{staticClass:\"clip-pages-wrapper\"},[_c('transition',{attrs:{\"name\":\"clip-current\"}},[_c('div',{key:'current-' + _vm.currentPage,staticClass:\"clip-page clip-page-current\"},[(_vm.pages[_vm.currentPage - 1])?_c('img',{staticClass:\"clip-page-image\",attrs:{\"src\":_vm.pages[_vm.currentPage - 1],\"alt\":\"\"}}):_c('div',{staticClass:\"clip-page-placeholder\"},[_vm._v(\"封面/封底\")])])]),(_vm.currentPage < _vm.totalPages)?_c('div',{staticClass:\"clip-page clip-page-next\"},[(_vm.pages[_vm.currentPage])?_c('img',{staticClass:\"clip-page-image\",attrs:{\"src\":_vm.pages[_vm.currentPage],\"alt\":\"\"}}):_c('div',{staticClass:\"clip-page-placeholder\"},[_vm._v(\"封面/封底\")])]):_vm._e()],1)]):(_vm.flipMode === 'card')?_c('div',{ref:\"cardViewer\",class:['card-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']},[_c('div',{staticClass:\"card-stack\"},[_c('transition-group',{staticClass:\"card-transition-group\",attrs:{\"name\":\"card-flip\",\"tag\":\"div\"}},_vm._l((_vm.visibleCards),function(page,index){return _c('div',{key:'card-' + page.originalIndex,staticClass:\"card-item\",class:{\n 'card-item-prev': page.position === 'prev',\n 'card-item-current': page.position === 'current',\n 'card-item-next': page.position === 'next'\n },style:(_vm.getCardStyle(page.position))},[_c('div',{staticClass:\"card-content\"},[(page.src)?_c('img',{staticClass:\"card-image\",attrs:{\"src\":page.src,\"alt\":\"\"}}):_c('div',{staticClass:\"card-placeholder\"},[_vm._v(\"封面/封底\")])]),_c('div',{staticClass:\"card-page-number\"},[_vm._v(_vm._s(page.originalIndex + 1)+\" / \"+_vm._s(_vm.totalPages))])])}),0)],1)]):_vm._e()],1):_vm._e(),_c('div',{staticClass:\"controls\",class:{ 'mobile-controls': _vm.isMobile, 'desktop-controls': !_vm.isMobile, 'controls-visible': _vm.showControls },on:{\"click\":function($event){$event.stopPropagation();}}},[_c('button',{staticClass:\"btn btn-prev\",on:{\"click\":_vm.flipLeft}},[_vm._v(\" ← 上一页 \")]),_c('span',{staticClass:\"page-indicator\"},[(_vm.totalPages === 0)?_c('span',[_vm._v(\"加载中...\")]):(_vm.currentPage === 1 && _vm.totalPages > 1)?_c('span',[_vm._v(\"封面\")]):(_vm.currentPage === _vm.totalPages && _vm.totalPages > 1)?_c('span',[_vm._v(\"封底\")]):(!_vm.isMobile && _vm.totalPages > 2)?_c('span',[_vm._v(\"第 \"+_vm._s(Math.ceil((_vm.currentPage - 1) / 2))+\" 组 / 共 \"+_vm._s(Math.ceil((_vm.totalPages - 2) / 2))+\" 组\")]):(_vm.isMobile && _vm.totalPages > 2)?_c('span',[_vm._v(\"第 \"+_vm._s(Math.max(1, _vm.currentPage ))+\" 页 / 共 \"+_vm._s(Math.max(0, _vm.totalPages - 2))+\" 页\")]):(_vm.totalPages === 1)?_c('span',[_vm._v(\"第 1 页 / 共 1 页\")]):_c('span',[_vm._v(\"第 \"+_vm._s(_vm.currentPage)+\" 页 / 共 \"+_vm._s(_vm.totalPages)+\" 页\")])]),_c('button',{staticClass:\"btn btn-next\",on:{\"click\":_vm.flipRight}},[_vm._v(\" 下一页 → \")])]),(_vm.showFlipModeSelector)?_c('div',{staticClass:\"flip-mode-selector\",class:{ 'mobile-flip-mode-selector': _vm.isMobile }},[_c('button',{staticClass:\"btn btn-flip-mode\",on:{\"click\":_vm.toggleFlipModeMenu}},[_c('span',{staticClass:\"flip-mode-icon\"},[_vm._v(_vm._s(_vm.flipModeIcon))]),_c('span',{staticClass:\"flip-mode-text\"},[_vm._v(_vm._s(_vm.flipModeLabel))])]),(_vm.showFlipModeMenu)?_c('div',{staticClass:\"flip-mode-menu\"},_vm._l((_vm.flipModes),function(mode){return _c('div',{key:mode.value,staticClass:\"flip-mode-item\",class:{ 'flip-mode-item-active': _vm.flipMode === mode.value },on:{\"click\":function($event){return _vm.selectFlipMode(mode.value)}}},[_c('span',{staticClass:\"flip-mode-item-icon\"},[_vm._v(_vm._s(mode.icon))]),_c('span',{staticClass:\"flip-mode-item-label\"},[_vm._v(_vm._s(mode.label))])])}),0):_vm._e()]):_vm._e(),_c('div',{staticClass:\"catalogue-button\",class:{\n 'mobile-catalogue-button': _vm.isMobile,\n 'dragging': _vm.catalogueButtonDragging,\n 'catalogue-visible': _vm.showControls\n },style:({\n left: _vm.catalogueButtonPosition.x + 'px',\n top: _vm.catalogueButtonPosition.y + 'px'\n }),on:{\"mousedown\":_vm.onCatalogueMouseDown,\"touchstart\":_vm.onCatalogueTouchStart,\"touchmove\":_vm.onCatalogueTouchMove,\"touchend\":_vm.onCatalogueTouchEnd,\"click\":function($event){$event.stopPropagation();}}},[_c('button',{staticClass:\"btn btn-catalogue\",on:{\"click\":_vm.openCatalogue}},[_c('span',{staticClass:\"catalogue-text\"},[_vm._v(\"目录\")])])]),_c('div',{staticClass:\"zoom-hint\",class:{ 'show': _vm.zoomScale !== 1 }},[_c('div',{staticClass:\"zoom-info\"},[_c('span',[_vm._v(\"缩放: \"+_vm._s(Math.round(_vm.zoomScale * 100))+\"%\")]),_c('button',{staticClass:\"btn-reset-zoom\",on:{\"click\":_vm.resetZoom}},[_vm._v(\"重置\")])])]),(!_vm.isMobile)?_c('div',{staticClass:\"zoom-toolbar\"},[_c('button',{staticClass:\"btn-zoom\",attrs:{\"disabled\":_vm.zoomScale <= 0.5},on:{\"click\":_vm.zoomOut}},[_vm._v(\"-\")]),_c('button',{staticClass:\"btn-zoom\",attrs:{\"disabled\":_vm.zoomScale >= 3},on:{\"click\":_vm.zoomIn}},[_vm._v(\"+\")])]):_vm._e(),(_vm.loading)?_c('div',{staticClass:\"loading-overlay\"},[_vm._m(0)]):_vm._e(),(_vm.error && !_vm.loading)?_c('div',{staticClass:\"error-container\"},[_c('div',{staticClass:\"error-content\"},[_c('div',{staticClass:\"error-icon\"},[_vm._v(\"⚠️\")]),_c('h3',[_vm._v(\"加载失败\")]),_c('p',[_vm._v(_vm._s(_vm.error.message || '网络连接异常,请检查网络后重试'))]),_c('button',{staticClass:\"retry-btn\",on:{\"click\":_vm.fetchBooksData}},[_c('span',{staticClass:\"retry-icon\"},[_vm._v(\"🔄\")]),_vm._v(\" 重新加载 \")])])]):_vm._e(),(_vm.booksData && !_vm.loading && !_vm.error)?_c('div',{staticClass:\"success-toast\",class:{ 'show': _vm.showSuccessToast }},[_vm._v(\" 书籍加载完成 \")]):_vm._e(),_c('book-catalogue-drawer',{attrs:{\"book-id\":_vm.bookId,\"catalogue\":_vm.catalogue,\"loading\":_vm.catalogueLoading},on:{\"catalogue-click\":_vm.onCatalogueClick,\"page-jump\":_vm.onPageJump,\"Focus\":_vm.onFocus,\"fetch-catalogue\":_vm.onFetchCatalogue},model:{value:(_vm.showCatalogueDrawer),callback:function ($$v) {_vm.showCatalogueDrawer=$$v},expression:\"showCatalogueDrawer\"}})],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"loading-container\"},[_c('div',{staticClass:\"loading-spinner\"},[_c('div',{staticClass:\"spinner-ring\"}),_c('div',{staticClass:\"spinner-ring\"}),_c('div',{staticClass:\"spinner-ring\"})]),_c('div',{staticClass:\"loading-text\"},[_c('h3',[_vm._v(\"正在加载书籍\")]),_c('p',[_vm._v(\"请稍候,正在获取精彩内容...\")]),_c('div',{staticClass:\"loading-progress\"},[_c('div',{staticClass:\"progress-bar\"})])])])\n}]\n\nexport { render, staticRenderFns }","export * from \"-!../../node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[2]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=template&id=23d695ae&scoped=true\"","/*! @license Rematrix v0.7.2\n\n\tCopyright 2021 Julian Lloyd.\n\n\tPermission is hereby granted, free of charge, to any person obtaining a copy\n\tof this software and associated documentation files (the \"Software\"), to deal\n\tin the Software without restriction, including without limitation the rights\n\tto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n\tcopies of the Software, and to permit persons to whom the Software is\n\tfurnished to do so, subject to the following conditions:\n\n\tThe above copyright notice and this permission notice shall be included in\n\tall copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n\tIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n\tFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n\tAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n\tLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n\tOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n\tTHE SOFTWARE.\n*/\nfunction format(source) {\n if (source && source.constructor === Array) {\n var values = source\n .filter(function (value) { return typeof value === 'number'; })\n .filter(function (value) { return !isNaN(value); });\n\n if (source.length === 6 && values.length === 6) {\n var matrix = identity();\n matrix[0] = values[0];\n matrix[1] = values[1];\n matrix[4] = values[2];\n matrix[5] = values[3];\n matrix[12] = values[4];\n matrix[13] = values[5];\n return matrix\n } else if (source.length === 16 && values.length === 16) {\n return source\n }\n }\n throw new TypeError('Expected a `number[]` with length 6 or 16.')\n}\n\nfunction fromString(source) {\n if (typeof source === 'string') {\n var match = source.match(/matrix(3d)?\\(([^)]+)\\)/);\n if (match) {\n var raw = match[2].split(',').map(parseFloat);\n return format(raw)\n }\n if (source === 'none' || source === '') {\n return identity()\n }\n }\n throw new TypeError('Expected a string containing `matrix()` or `matrix3d()')\n}\n\nfunction identity() {\n var matrix = [];\n for (var i = 0; i < 16; i++) {\n i % 5 == 0 ? matrix.push(1) : matrix.push(0);\n }\n return matrix\n}\n\nfunction inverse(source) {\n var m = format(source);\n\n var s0 = m[0] * m[5] - m[4] * m[1];\n var s1 = m[0] * m[6] - m[4] * m[2];\n var s2 = m[0] * m[7] - m[4] * m[3];\n var s3 = m[1] * m[6] - m[5] * m[2];\n var s4 = m[1] * m[7] - m[5] * m[3];\n var s5 = m[2] * m[7] - m[6] * m[3];\n\n var c5 = m[10] * m[15] - m[14] * m[11];\n var c4 = m[9] * m[15] - m[13] * m[11];\n var c3 = m[9] * m[14] - m[13] * m[10];\n var c2 = m[8] * m[15] - m[12] * m[11];\n var c1 = m[8] * m[14] - m[12] * m[10];\n var c0 = m[8] * m[13] - m[12] * m[9];\n\n var determinant = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0);\n\n if (isNaN(determinant) || determinant === Infinity) {\n throw new Error('Inverse determinant attempted to divide by zero.')\n }\n\n return [\n (m[5] * c5 - m[6] * c4 + m[7] * c3) * determinant,\n (-m[1] * c5 + m[2] * c4 - m[3] * c3) * determinant,\n (m[13] * s5 - m[14] * s4 + m[15] * s3) * determinant,\n (-m[9] * s5 + m[10] * s4 - m[11] * s3) * determinant,\n\n (-m[4] * c5 + m[6] * c2 - m[7] * c1) * determinant,\n (m[0] * c5 - m[2] * c2 + m[3] * c1) * determinant,\n (-m[12] * s5 + m[14] * s2 - m[15] * s1) * determinant,\n (m[8] * s5 - m[10] * s2 + m[11] * s1) * determinant,\n\n (m[4] * c4 - m[5] * c2 + m[7] * c0) * determinant,\n (-m[0] * c4 + m[1] * c2 - m[3] * c0) * determinant,\n (m[12] * s4 - m[13] * s2 + m[15] * s0) * determinant,\n (-m[8] * s4 + m[9] * s2 - m[11] * s0) * determinant,\n\n (-m[4] * c3 + m[5] * c1 - m[6] * c0) * determinant,\n (m[0] * c3 - m[1] * c1 + m[2] * c0) * determinant,\n (-m[12] * s3 + m[13] * s1 - m[14] * s0) * determinant,\n (m[8] * s3 - m[9] * s1 + m[10] * s0) * determinant ]\n}\n\nfunction multiply(matrixA, matrixB) {\n var fma = format(matrixA);\n var fmb = format(matrixB);\n var product = [];\n\n for (var i = 0; i < 4; i++) {\n var row = [fma[i], fma[i + 4], fma[i + 8], fma[i + 12]];\n for (var j = 0; j < 4; j++) {\n var k = j * 4;\n var col = [fmb[k], fmb[k + 1], fmb[k + 2], fmb[k + 3]];\n var result = row[0] * col[0] + row[1] * col[1] + row[2] * col[2] + row[3] * col[3];\n\n product[i + k] = result;\n }\n }\n\n return product\n}\n\nfunction perspective(distance) {\n var matrix = identity();\n matrix[11] = -1 / distance;\n return matrix\n}\n\nfunction rotate(angle) {\n return rotateZ(angle)\n}\n\nfunction rotateX(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[5] = matrix[10] = Math.cos(theta);\n matrix[6] = matrix[9] = Math.sin(theta);\n matrix[9] *= -1;\n\n return matrix\n}\n\nfunction rotateY(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[0] = matrix[10] = Math.cos(theta);\n matrix[2] = matrix[8] = Math.sin(theta);\n matrix[2] *= -1;\n\n return matrix\n}\n\nfunction rotateZ(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[0] = matrix[5] = Math.cos(theta);\n matrix[1] = matrix[4] = Math.sin(theta);\n matrix[4] *= -1;\n\n return matrix\n}\n\nfunction scale(scalar, scalarY) {\n var matrix = identity();\n\n matrix[0] = scalar;\n matrix[5] = typeof scalarY === 'number' ? scalarY : scalar;\n\n return matrix\n}\n\nfunction scaleX(scalar) {\n var matrix = identity();\n matrix[0] = scalar;\n return matrix\n}\n\nfunction scaleY(scalar) {\n var matrix = identity();\n matrix[5] = scalar;\n return matrix\n}\n\nfunction scaleZ(scalar) {\n var matrix = identity();\n matrix[10] = scalar;\n return matrix\n}\n\nfunction skew(angleX, angleY) {\n var thetaX = (Math.PI / 180) * angleX;\n var matrix = identity();\n\n matrix[4] = Math.tan(thetaX);\n\n if (angleY) {\n var thetaY = (Math.PI / 180) * angleY;\n matrix[1] = Math.tan(thetaY);\n }\n\n return matrix\n}\n\nfunction skewX(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[4] = Math.tan(theta);\n\n return matrix\n}\n\nfunction skewY(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[1] = Math.tan(theta);\n\n return matrix\n}\n\nfunction toString(source) {\n return (\"matrix3d(\" + (format(source).join(', ')) + \")\")\n}\n\nfunction translate(distanceX, distanceY) {\n var matrix = identity();\n matrix[12] = distanceX;\n\n if (distanceY) {\n matrix[13] = distanceY;\n }\n\n return matrix\n}\n\nfunction translate3d(distanceX, distanceY, distanceZ) {\n var matrix = identity();\n if (distanceX !== undefined && distanceY !== undefined && distanceZ !== undefined) {\n matrix[12] = distanceX;\n matrix[13] = distanceY;\n matrix[14] = distanceZ;\n }\n return matrix\n}\n\nfunction translateX(distance) {\n var matrix = identity();\n matrix[12] = distance;\n return matrix\n}\n\nfunction translateY(distance) {\n var matrix = identity();\n matrix[13] = distance;\n return matrix\n}\n\nfunction translateZ(distance) {\n var matrix = identity();\n matrix[14] = distance;\n return matrix\n}\n\nexport { format, fromString, identity, inverse, multiply, perspective, rotate, rotateX, rotateY, rotateZ, scale, scaleX, scaleY, scaleZ, skew, skewX, skewY, toString, translate, translate3d, translateX, translateY, translateZ };\n","/*!\n * @license\n * flipbook-vue v1.0.0-beta.4\n * Copyright © 2023 Takeshi Sone.\n * Released under the MIT License.\n */\n\nimport { multiply, perspective, translate, translate3d, rotateY, toString, identity } from 'rematrix';\n\nvar Matrix = /*@__PURE__*/(function () {\n function Matrix(arg) {\n if (arg) {\n if (arg.m) {\n this.m = [].concat( arg.m );\n } else {\n this.m = [].concat( arg );\n }\n } else {\n this.m = identity();\n }\n }\n\n Matrix.prototype.clone = function clone () {\n return new Matrix(this);\n };\n\n Matrix.prototype.multiply = function multiply$1 (m) {\n return this.m = multiply(this.m, m);\n };\n\n Matrix.prototype.perspective = function perspective$1 (d) {\n return this.multiply(perspective(d));\n };\n\n Matrix.prototype.transformX = function transformX (x) {\n return (x * this.m[0] + this.m[12]) / (x * this.m[3] + this.m[15]);\n };\n\n Matrix.prototype.translate = function translate$1 (x, y) {\n return this.multiply(translate(x, y));\n };\n\n Matrix.prototype.translate3d = function translate3d$1 (x, y, z) {\n return this.multiply(translate3d(x, y, z));\n };\n\n Matrix.prototype.rotateY = function rotateY$1 (deg) {\n return this.multiply(rotateY(deg));\n };\n\n Matrix.prototype.toString = function toString$1 () {\n return toString(this.m);\n };\n\n return Matrix;\n}());\n\nvar spinner = \"data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22500%22%20height%3D%22500%22%20viewBox%3D%220%200%20500%20500%22%20fill%3D%22transparent%22%20style%3D%22background-color%3A%20%23fff%22%3E%20%20%3Ccircle%20%20%20%20cx%3D%22250%22%20%20%20%20cy%3D%22250%22%20%20%20%20r%3D%2248%22%20%20%20%20stroke%3D%22%23333%22%20%20%20%20stroke-width%3D%222%22%20%20%20%20stroke-dasharray%3D%22271%2030%22%20%20%3E%20%20%20%20%3CanimateTransform%20%20%20%20%20%20attributeName%3D%22transform%22%20%20%20%20%20%20attributeType%3D%22XML%22%20%20%20%20%20%20type%3D%22rotate%22%20%20%20%20%20%20from%3D%220%20250%20250%22%20%20%20%20%20%20to%3D%22360%20250%20250%22%20%20%20%20%20%20dur%3D%221s%22%20%20%20%20%20%20repeatCount%3D%22indefinite%22%20%20%20%20%2F%3E%20%20%3C%2Fcircle%3E%3C%2Fsvg%3E\";\n\nvar easeIn, easeInOut, easeOut;\n\neaseIn = function(x) {\n return Math.pow(x, 2);\n};\n\neaseOut = function(x) {\n return 1 - easeIn(1 - x);\n};\n\neaseInOut = function(x) {\n if (x < 0.5) {\n return easeIn(x * 2) / 2;\n } else {\n return 0.5 + easeOut((x - 0.5) * 2) / 2;\n }\n};\n\nvar script = {\n props: {\n pages: {\n type: Array,\n required: true\n },\n pagesHiRes: {\n type: Array,\n default: function() {\n return [];\n }\n },\n flipDuration: {\n type: Number,\n default: 1000\n },\n zoomDuration: {\n type: Number,\n default: 500\n },\n zooms: {\n type: Array,\n default: function() {\n return [1, 2, 4];\n }\n },\n perspective: {\n type: Number,\n default: 2400\n },\n nPolygons: {\n type: Number,\n default: 10\n },\n ambient: {\n type: Number,\n default: 0.4\n },\n gloss: {\n type: Number,\n default: 0.6\n },\n swipeMin: {\n type: Number,\n default: 3\n },\n singlePage: {\n type: Boolean,\n default: false\n },\n forwardDirection: {\n validator: function(val) {\n return val === 'right' || val === 'left';\n },\n default: 'right'\n },\n centering: {\n type: Boolean,\n default: true\n },\n startPage: {\n type: Number,\n default: null\n },\n loadingImage: {\n type: String,\n default: spinner\n },\n clickToZoom: {\n type: Boolean,\n default: true\n },\n dragToFlip: {\n type: Boolean,\n default: true\n },\n wheel: {\n type: String,\n default: 'scroll'\n }\n },\n data: function() {\n return {\n viewWidth: 0,\n viewHeight: 0,\n imageWidth: null,\n imageHeight: null,\n displayedPages: 1,\n nImageLoad: 0,\n nImageLoadTrigger: 0,\n imageLoadCallback: null,\n currentPage: 0,\n firstPage: 0,\n secondPage: 1,\n zoomIndex: 0,\n zoom: 1,\n zooming: false,\n touchStartX: null,\n touchStartY: null,\n maxMove: 0,\n activeCursor: null,\n hasTouchEvents: false,\n hasPointerEvents: false,\n minX: 2e308,\n maxX: -2e308,\n preloadedImages: {},\n flip: {\n progress: 0,\n direction: null,\n frontImage: null,\n backImage: null,\n auto: false,\n opacity: 1\n },\n currentCenterOffset: null,\n animatingCenter: false,\n startScrollLeft: 0,\n startScrollTop: 0,\n scrollLeft: 0,\n scrollTop: 0,\n loadedImages: {}\n };\n },\n computed: {\n IE: function() {\n return typeof navigator !== 'undefined' && /Trident/.test(navigator.userAgent);\n },\n canFlipLeft: function() {\n if (this.forwardDirection === 'left') {\n return this.canGoForward;\n } else {\n return this.canGoBack;\n }\n },\n canFlipRight: function() {\n if (this.forwardDirection === 'right') {\n return this.canGoForward;\n } else {\n return this.canGoBack;\n }\n },\n canZoomIn: function() {\n return !this.zooming && this.zoomIndex < this.zooms_.length - 1;\n },\n canZoomOut: function() {\n return !this.zooming && this.zoomIndex > 0;\n },\n numPages: function() {\n if (this.pages[0] === null) {\n return this.pages.length - 1;\n } else {\n return this.pages.length;\n }\n },\n page: function() {\n if (this.pages[0] !== null) {\n return this.currentPage + 1;\n } else {\n return Math.max(1, this.currentPage);\n }\n },\n zooms_: function() {\n return this.zooms || [1];\n },\n canGoForward: function() {\n return !this.flip.direction && this.currentPage < this.pages.length - this.displayedPages;\n },\n canGoBack: function() {\n return !this.flip.direction && this.currentPage >= this.displayedPages && !(this.displayedPages === 1 && !this.pageUrl(this.firstPage - 1));\n },\n leftPage: function() {\n if (this.forwardDirection === 'right' || this.displayedPages === 1) {\n return this.firstPage;\n } else {\n return this.secondPage;\n }\n },\n rightPage: function() {\n if (this.forwardDirection === 'left') {\n return this.firstPage;\n } else {\n return this.secondPage;\n }\n },\n showLeftPage: function() {\n return this.pageUrl(this.leftPage);\n },\n showRightPage: function() {\n return this.pageUrl(this.rightPage) && this.displayedPages === 2;\n },\n cursor: function() {\n if (this.activeCursor) {\n return this.activeCursor;\n } else if (this.IE) {\n return 'auto';\n } else if (this.clickToZoom && this.canZoomIn) {\n return 'zoom-in';\n } else if (this.clickToZoom && this.canZoomOut) {\n return 'zoom-out';\n } else if (this.dragToFlip) {\n return 'grab';\n } else {\n return 'auto';\n }\n },\n pageScale: function() {\n var scale, vw, xScale, yScale;\n vw = this.viewWidth / this.displayedPages;\n xScale = vw / this.imageWidth;\n yScale = this.viewHeight / this.imageHeight;\n scale = xScale < yScale ? xScale : yScale;\n if (scale < 1) {\n return scale;\n } else {\n return 1;\n }\n },\n pageWidth: function() {\n return Math.round(this.imageWidth * this.pageScale);\n },\n pageHeight: function() {\n return Math.round(this.imageHeight * this.pageScale);\n },\n xMargin: function() {\n return (this.viewWidth - this.pageWidth * this.displayedPages) / 2;\n },\n yMargin: function() {\n return (this.viewHeight - this.pageHeight) / 2;\n },\n polygonWidth: function() {\n var w;\n w = this.pageWidth / this.nPolygons;\n w = Math.ceil(w + 1 / this.zoom);\n return w + 'px';\n },\n polygonHeight: function() {\n return this.pageHeight + 'px';\n },\n polygonBgSize: function() {\n return ((this.pageWidth) + \"px \" + (this.pageHeight) + \"px\");\n },\n polygonArray: function() {\n return this.makePolygonArray('front').concat(this.makePolygonArray('back'));\n },\n boundingLeft: function() {\n var x;\n if (this.displayedPages === 1) {\n return this.xMargin;\n } else {\n x = this.pageUrl(this.leftPage) ? this.xMargin : this.viewWidth / 2;\n if (x < this.minX) {\n return x;\n } else {\n return this.minX;\n }\n }\n },\n boundingRight: function() {\n var x;\n if (this.displayedPages === 1) {\n return this.viewWidth - this.xMargin;\n } else {\n x = this.pageUrl(this.rightPage) ? this.viewWidth - this.xMargin : this.viewWidth / 2;\n if (x > this.maxX) {\n return x;\n } else {\n return this.maxX;\n }\n }\n },\n centerOffset: function() {\n var retval;\n retval = this.centering ? Math.round(this.viewWidth / 2 - (this.boundingLeft + this.boundingRight) / 2) : 0;\n if (this.currentCenterOffset === null && this.imageWidth !== null) {\n this.currentCenterOffset = retval;\n }\n return retval;\n },\n centerOffsetSmoothed: function() {\n return Math.round(this.currentCenterOffset);\n },\n dragToScroll: function() {\n return !this.hasTouchEvents;\n },\n scrollLeftMin: function() {\n var w;\n w = (this.boundingRight - this.boundingLeft) * this.zoom;\n if (w < this.viewWidth) {\n return (this.boundingLeft + this.centerOffsetSmoothed) * this.zoom - (this.viewWidth - w) / 2;\n } else {\n return (this.boundingLeft + this.centerOffsetSmoothed) * this.zoom;\n }\n },\n scrollLeftMax: function() {\n var w;\n w = (this.boundingRight - this.boundingLeft) * this.zoom;\n if (w < this.viewWidth) {\n return (this.boundingLeft + this.centerOffsetSmoothed) * this.zoom - (this.viewWidth - w) / 2;\n } else {\n return (this.boundingRight + this.centerOffsetSmoothed) * this.zoom - this.viewWidth;\n }\n },\n scrollTopMin: function() {\n var h;\n h = this.pageHeight * this.zoom;\n if (h < this.viewHeight) {\n return this.yMargin * this.zoom - (this.viewHeight - h) / 2;\n } else {\n return this.yMargin * this.zoom;\n }\n },\n scrollTopMax: function() {\n var h;\n h = this.pageHeight * this.zoom;\n if (h < this.viewHeight) {\n return this.yMargin * this.zoom - (this.viewHeight - h) / 2;\n } else {\n return (this.yMargin + this.pageHeight) * this.zoom - this.viewHeight;\n }\n },\n scrollLeftLimited: function() {\n return Math.min(this.scrollLeftMax, Math.max(this.scrollLeftMin, this.scrollLeft));\n },\n scrollTopLimited: function() {\n return Math.min(this.scrollTopMax, Math.max(this.scrollTopMin, this.scrollTop));\n }\n },\n mounted: function() {\n window.addEventListener('resize', this.onResize, {\n passive: true\n });\n this.onResize();\n this.zoom = this.zooms_[0];\n return this.goToPage(this.startPage);\n },\n beforeDestroy: function() {\n return window.removeEventListener('resize', this.onResize, {\n passive: true\n });\n },\n methods: {\n onResize: function() {\n var viewport;\n viewport = this.$refs.viewport;\n if (!viewport) {\n return;\n }\n this.viewWidth = viewport.clientWidth;\n this.viewHeight = viewport.clientHeight;\n this.displayedPages = this.viewWidth > this.viewHeight && !this.singlePage ? 2 : 1;\n if (this.displayedPages === 2) {\n this.currentPage &= ~1;\n }\n this.fixFirstPage();\n this.minX = 2e308;\n return this.maxX = -2e308;\n },\n fixFirstPage: function() {\n if (this.displayedPages === 1 && this.currentPage === 0 && this.pages.length && !this.pageUrl(0)) {\n return this.currentPage++;\n }\n },\n pageUrl: function(page, hiRes) {\n if ( hiRes === void 0 ) hiRes = false;\n\n var url;\n if (hiRes && this.zoom > 1 && !this.zooming) {\n url = this.pagesHiRes[page];\n if (url) {\n return url;\n }\n }\n return this.pages[page] || null;\n },\n pageUrlLoading: function(page, hiRes) {\n if ( hiRes === void 0 ) hiRes = false;\n\n var url;\n url = this.pageUrl(page, hiRes);\n if (hiRes && this.zoom > 1 && !this.zooming) {\n // High-res image doesn't use 'loading'\n return url;\n }\n return url && this.loadImage(url);\n },\n flipLeft: function() {\n if (!this.canFlipLeft) {\n return;\n }\n return this.flipStart('left', true);\n },\n flipRight: function() {\n if (!this.canFlipRight) {\n return;\n }\n return this.flipStart('right', true);\n },\n makePolygonArray: function(face) {\n var bgPos, dRadian, dRotate, direction, i, image, j, lighting, m, originRight, pageMatrix, pageRotation, pageX, polygonWidth, progress, rad, radian, radius, ref, results, rotate, theta, x, x0, x1, z;\n if (!this.flip.direction) {\n return [];\n }\n progress = this.flip.progress;\n direction = this.flip.direction;\n if (this.displayedPages === 1 && direction !== this.forwardDirection) {\n progress = 1 - progress;\n direction = this.forwardDirection;\n }\n this.flip.opacity = this.displayedPages === 1 && progress > .7 ? 1 - (progress - .7) / .3 : 1;\n image = face === 'front' ? this.flip.frontImage : this.flip.backImage;\n polygonWidth = this.pageWidth / this.nPolygons;\n pageX = this.xMargin;\n originRight = false;\n if (this.displayedPages === 1) {\n if (this.forwardDirection === 'right') {\n if (face === 'back') {\n originRight = true;\n pageX = this.xMargin - this.pageWidth;\n }\n } else {\n if (direction === 'left') {\n if (face === 'back') {\n pageX = this.pageWidth - this.xMargin;\n } else {\n originRight = true;\n }\n } else {\n if (face === 'front') {\n pageX = this.pageWidth - this.xMargin;\n } else {\n originRight = true;\n }\n }\n }\n } else {\n if (direction === 'left') {\n if (face === 'back') {\n pageX = this.viewWidth / 2;\n } else {\n originRight = true;\n }\n } else {\n if (face === 'front') {\n pageX = this.viewWidth / 2;\n } else {\n originRight = true;\n }\n }\n }\n pageMatrix = new Matrix();\n pageMatrix.translate(this.viewWidth / 2);\n pageMatrix.perspective(this.perspective);\n pageMatrix.translate(-this.viewWidth / 2);\n pageMatrix.translate(pageX, this.yMargin);\n pageRotation = 0;\n if (progress > 0.5) {\n pageRotation = -(progress - 0.5) * 2 * 180;\n }\n if (direction === 'left') {\n pageRotation = -pageRotation;\n }\n if (face === 'back') {\n pageRotation += 180;\n }\n if (pageRotation) {\n if (originRight) {\n pageMatrix.translate(this.pageWidth);\n }\n pageMatrix.rotateY(pageRotation);\n if (originRight) {\n pageMatrix.translate(-this.pageWidth);\n }\n }\n if (progress < 0.5) {\n theta = progress * 2 * Math.PI;\n } else {\n theta = (1 - (progress - 0.5) * 2) * Math.PI;\n }\n if (theta === 0) {\n theta = 1e-9;\n }\n radius = this.pageWidth / theta;\n radian = 0;\n dRadian = theta / this.nPolygons;\n rotate = dRadian / 2 / Math.PI * 180;\n dRotate = dRadian / Math.PI * 180;\n if (originRight) {\n rotate = -theta / Math.PI * 180 + dRotate / 2;\n }\n if (face === 'back') {\n rotate = -rotate;\n dRotate = -dRotate;\n }\n this.minX = 2e308;\n this.maxX = -2e308;\n results = [];\n for (i = j = 0, ref = this.nPolygons; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {\n bgPos = (i / (this.nPolygons - 1) * 100) + \"% 0px\";\n m = pageMatrix.clone();\n rad = originRight ? theta - radian : radian;\n x = Math.sin(rad) * radius;\n if (originRight) {\n x = this.pageWidth - x;\n }\n z = (1 - Math.cos(rad)) * radius;\n if (face === 'back') {\n z = -z;\n }\n m.translate3d(x, 0, z);\n m.rotateY(-rotate);\n x0 = m.transformX(0);\n x1 = m.transformX(polygonWidth);\n this.maxX = Math.max(Math.max(x0, x1), this.maxX);\n this.minX = Math.min(Math.min(x0, x1), this.minX);\n lighting = this.computeLighting(pageRotation - rotate, dRotate);\n radian += dRadian;\n rotate += dRotate;\n results.push([face + i, image, lighting, bgPos, m.toString(), Math.abs(Math.round(z))]);\n }\n return results;\n },\n computeLighting: function(rot, dRotate) {\n var DEG, POW, blackness, diffuse, gradients, lightingPoints, specular;\n gradients = [];\n lightingPoints = [-0.5, -0.25, 0, 0.25, 0.5];\n if (this.ambient < 1) {\n blackness = 1 - this.ambient;\n diffuse = lightingPoints.map(function (d) {\n return (1 - Math.cos((rot - dRotate * d) / 180 * Math.PI)) * blackness;\n });\n gradients.push((\"linear-gradient(to right,\\n rgba(0, 0, 0, \" + (diffuse[0]) + \"),\\n rgba(0, 0, 0, \" + (diffuse[1]) + \") 25%,\\n rgba(0, 0, 0, \" + (diffuse[2]) + \") 50%,\\n rgba(0, 0, 0, \" + (diffuse[3]) + \") 75%,\\n rgba(0, 0, 0, \" + (diffuse[4]) + \"))\"));\n }\n if (this.gloss > 0 && !this.IE) {\n DEG = 30;\n POW = 200;\n specular = lightingPoints.map(function (d) {\n return Math.max(Math.pow( Math.cos((rot + DEG - dRotate * d) / 180 * Math.PI), POW ), Math.pow( Math.cos((rot - DEG - dRotate * d) / 180 * Math.PI), POW ));\n });\n gradients.push((\"linear-gradient(to right,\\n rgba(255, 255, 255, \" + (specular[0] * this.gloss) + \"),\\n rgba(255, 255, 255, \" + (specular[1] * this.gloss) + \") 25%,\\n rgba(255, 255, 255, \" + (specular[2] * this.gloss) + \") 50%,\\n rgba(255, 255, 255, \" + (specular[3] * this.gloss) + \") 75%,\\n rgba(255, 255, 255, \" + (specular[4] * this.gloss) + \"))\"));\n }\n return gradients.join(',');\n },\n flipStart: function(direction, auto) {\n var this$1$1 = this;\n\n if (direction !== this.forwardDirection) {\n if (this.displayedPages === 1) {\n this.flip.frontImage = this.pageUrl(this.currentPage - 1);\n this.flip.backImage = null;\n } else {\n this.flip.frontImage = this.pageUrl(this.firstPage);\n this.flip.backImage = this.pageUrl(this.currentPage - this.displayedPages + 1);\n }\n } else {\n if (this.displayedPages === 1) {\n this.flip.frontImage = this.pageUrl(this.currentPage);\n this.flip.backImage = null;\n } else {\n this.flip.frontImage = this.pageUrl(this.secondPage);\n this.flip.backImage = this.pageUrl(this.currentPage + this.displayedPages);\n }\n }\n this.flip.direction = direction;\n this.flip.progress = 0;\n return requestAnimationFrame(function () {\n return requestAnimationFrame(function () {\n if (this$1$1.flip.direction !== this$1$1.forwardDirection) {\n if (this$1$1.displayedPages === 2) {\n this$1$1.firstPage = this$1$1.currentPage - this$1$1.displayedPages;\n }\n } else {\n if (this$1$1.displayedPages === 1) {\n this$1$1.firstPage = this$1$1.currentPage + this$1$1.displayedPages;\n } else {\n this$1$1.secondPage = this$1$1.currentPage + 1 + this$1$1.displayedPages;\n }\n }\n if (auto) {\n return this$1$1.flipAuto(true);\n }\n });\n });\n },\n flipAuto: function(ease) {\n var this$1$1 = this;\n\n var animate, duration, startRatio, t0;\n t0 = Date.now();\n duration = this.flipDuration * (1 - this.flip.progress);\n startRatio = this.flip.progress;\n this.flip.auto = true;\n this.$emit((\"flip-\" + (this.flip.direction) + \"-start\"), this.page);\n animate = function () {\n return requestAnimationFrame(function () {\n var ratio, t;\n t = Date.now() - t0;\n ratio = startRatio + t / duration;\n if (ratio > 1) {\n ratio = 1;\n }\n this$1$1.flip.progress = ease ? easeInOut(ratio) : ratio;\n if (ratio < 1) {\n return animate();\n } else {\n if (this$1$1.flip.direction !== this$1$1.forwardDirection) {\n this$1$1.currentPage -= this$1$1.displayedPages;\n } else {\n this$1$1.currentPage += this$1$1.displayedPages;\n }\n this$1$1.$emit((\"flip-\" + (this$1$1.flip.direction) + \"-end\"), this$1$1.page);\n if (this$1$1.displayedPages === 1 && this$1$1.flip.direction === this$1$1.forwardDirection) {\n this$1$1.flip.direction = null;\n } else {\n this$1$1.onImageLoad(1, function () {\n return this$1$1.flip.direction = null;\n });\n }\n return this$1$1.flip.auto = false;\n }\n });\n };\n return animate();\n },\n flipRevert: function() {\n var this$1$1 = this;\n\n var animate, duration, startRatio, t0;\n t0 = Date.now();\n duration = this.flipDuration * this.flip.progress;\n startRatio = this.flip.progress;\n this.flip.auto = true;\n animate = function () {\n return requestAnimationFrame(function () {\n var ratio, t;\n t = Date.now() - t0;\n ratio = startRatio - startRatio * t / duration;\n if (ratio < 0) {\n ratio = 0;\n }\n this$1$1.flip.progress = ratio;\n if (ratio > 0) {\n return animate();\n } else {\n this$1$1.firstPage = this$1$1.currentPage;\n this$1$1.secondPage = this$1$1.currentPage + 1;\n if (this$1$1.displayedPages === 1 && this$1$1.flip.direction !== this$1$1.forwardDirection) {\n this$1$1.flip.direction = null;\n } else {\n this$1$1.onImageLoad(1, function () {\n return this$1$1.flip.direction = null;\n });\n }\n return this$1$1.flip.auto = false;\n }\n });\n };\n return animate();\n },\n onImageLoad: function(trigger, cb) {\n this.nImageLoad = 0;\n this.nImageLoadTrigger = trigger;\n return this.imageLoadCallback = cb;\n },\n didLoadImage: function(ev) {\n if (this.imageWidth === null) {\n this.imageWidth = (ev.target || ev.path[0]).naturalWidth;\n this.imageHeight = (ev.target || ev.path[0]).naturalHeight;\n this.preloadImages();\n }\n if (!this.imageLoadCallback) {\n return;\n }\n if (++this.nImageLoad >= this.nImageLoadTrigger) {\n this.imageLoadCallback();\n return this.imageLoadCallback = null;\n }\n },\n zoomIn: function(zoomAt) {\n if ( zoomAt === void 0 ) zoomAt = null;\n\n if (!this.canZoomIn) {\n return;\n }\n this.zoomIndex += 1;\n return this.zoomTo(this.zooms_[this.zoomIndex], zoomAt);\n },\n zoomOut: function(zoomAt) {\n if ( zoomAt === void 0 ) zoomAt = null;\n\n if (!this.canZoomOut) {\n return;\n }\n this.zoomIndex -= 1;\n return this.zoomTo(this.zooms_[this.zoomIndex], zoomAt);\n },\n zoomTo: function(zoom, zoomAt) {\n var this$1$1 = this;\n if ( zoomAt === void 0 ) zoomAt = null;\n\n var animate, containerFixedX, containerFixedY, end, endX, endY, fixedX, fixedY, rect, start, startX, startY, t0, viewport;\n viewport = this.$refs.viewport;\n if (zoomAt) {\n rect = viewport.getBoundingClientRect();\n fixedX = zoomAt.pageX - rect.left;\n fixedY = zoomAt.pageY - rect.top;\n } else {\n fixedX = viewport.clientWidth / 2;\n fixedY = viewport.clientHeight / 2;\n }\n start = this.zoom;\n end = zoom;\n startX = viewport.scrollLeft;\n startY = viewport.scrollTop;\n containerFixedX = fixedX + startX;\n containerFixedY = fixedY + startY;\n endX = containerFixedX / start * end - fixedX;\n endY = containerFixedY / start * end - fixedY;\n t0 = Date.now();\n this.zooming = true;\n this.$emit('zoom-start', zoom);\n animate = function () {\n return requestAnimationFrame(function () {\n var ratio, t;\n t = Date.now() - t0;\n ratio = t / this$1$1.zoomDuration;\n if (ratio > 1 || this$1$1.IE) {\n ratio = 1;\n }\n ratio = easeInOut(ratio);\n this$1$1.zoom = start + (end - start) * ratio;\n this$1$1.scrollLeft = startX + (endX - startX) * ratio;\n this$1$1.scrollTop = startY + (endY - startY) * ratio;\n if (t < this$1$1.zoomDuration) {\n return animate();\n } else {\n this$1$1.$emit('zoom-end', zoom);\n this$1$1.zooming = false;\n this$1$1.zoom = zoom;\n this$1$1.scrollLeft = endX;\n return this$1$1.scrollTop = endY;\n }\n });\n };\n animate();\n if (end > 1) {\n return this.preloadImages(true);\n }\n },\n zoomAt: function(zoomAt) {\n this.zoomIndex = (this.zoomIndex + 1) % this.zooms_.length;\n return this.zoomTo(this.zooms_[this.zoomIndex], zoomAt);\n },\n swipeStart: function(touch) {\n this.touchStartX = touch.pageX;\n this.touchStartY = touch.pageY;\n this.maxMove = 0;\n if (this.zoom <= 1) {\n if (this.dragToFlip) {\n return this.activeCursor = 'grab';\n }\n } else {\n this.startScrollLeft = this.$refs.viewport.scrollLeft;\n this.startScrollTop = this.$refs.viewport.scrollTop;\n return this.activeCursor = 'all-scroll';\n }\n },\n swipeMove: function(touch) {\n var x, y;\n if (this.touchStartX == null) {\n return;\n }\n x = touch.pageX - this.touchStartX;\n y = touch.pageY - this.touchStartY;\n this.maxMove = Math.max(this.maxMove, Math.abs(x));\n this.maxMove = Math.max(this.maxMove, Math.abs(y));\n if (this.zoom > 1) {\n if (this.dragToScroll) {\n this.dragScroll(x, y);\n }\n return;\n }\n if (!this.dragToFlip) {\n return;\n }\n if (Math.abs(y) > Math.abs(x)) {\n return;\n }\n this.activeCursor = 'grabbing';\n if (x > 0) {\n if (this.flip.direction === null && this.canFlipLeft && x >= this.swipeMin) {\n this.flipStart('left', false);\n }\n if (this.flip.direction === 'left') {\n this.flip.progress = x / this.pageWidth;\n if (this.flip.progress > 1) {\n this.flip.progress = 1;\n }\n }\n } else {\n if (this.flip.direction === null && this.canFlipRight && x <= -this.swipeMin) {\n this.flipStart('right', false);\n }\n if (this.flip.direction === 'right') {\n this.flip.progress = -x / this.pageWidth;\n if (this.flip.progress > 1) {\n this.flip.progress = 1;\n }\n }\n }\n return true;\n },\n swipeEnd: function(touch) {\n if (this.touchStartX == null) {\n return;\n }\n if (this.clickToZoom && this.maxMove < this.swipeMin) {\n this.zoomAt(touch);\n }\n if (this.flip.direction !== null && !this.flip.auto) {\n if (this.flip.progress > 1 / 4) {\n this.flipAuto(false);\n } else {\n this.flipRevert();\n }\n }\n this.touchStartX = null;\n return this.activeCursor = null;\n },\n onTouchStart: function(ev) {\n this.hasTouchEvents = true;\n return this.swipeStart(ev.changedTouches[0]);\n },\n onTouchMove: function(ev) {\n if (this.swipeMove(ev.changedTouches[0])) {\n if (ev.cancelable) {\n return ev.preventDefault();\n }\n }\n },\n onTouchEnd: function(ev) {\n return this.swipeEnd(ev.changedTouches[0]);\n },\n onPointerDown: function(ev) {\n this.hasPointerEvents = true;\n if (this.hasTouchEvents) {\n return;\n }\n if (ev.which && ev.which !== 1) { // Ignore right-click\n return;\n }\n this.swipeStart(ev);\n try {\n return ev.target.setPointerCapture(ev.pointerId);\n } catch (error) {\n\n }\n },\n onPointerMove: function(ev) {\n if (!this.hasTouchEvents) {\n return this.swipeMove(ev);\n }\n },\n onPointerUp: function(ev) {\n if (this.hasTouchEvents) {\n return;\n }\n this.swipeEnd(ev);\n try {\n return ev.target.releasePointerCapture(ev.pointerId);\n } catch (error) {\n\n }\n },\n onMouseDown: function(ev) {\n if (this.hasTouchEvents || this.hasPointerEvents) {\n return;\n }\n if (ev.which && ev.which !== 1) { // Ignore right-click\n return;\n }\n return this.swipeStart(ev);\n },\n onMouseMove: function(ev) {\n if (!(this.hasTouchEvents || this.hasPointerEvents)) {\n return this.swipeMove(ev);\n }\n },\n onMouseUp: function(ev) {\n if (!(this.hasTouchEvents || this.hasPointerEvents)) {\n return this.swipeEnd(ev);\n }\n },\n dragScroll: function(x, y) {\n this.scrollLeft = this.startScrollLeft - x;\n return this.scrollTop = this.startScrollTop - y;\n },\n onWheel: function(ev) {\n if (this.wheel === 'scroll' && this.zoom > 1 && this.dragToScroll) {\n this.scrollLeft = this.$refs.viewport.scrollLeft + ev.deltaX;\n this.scrollTop = this.$refs.viewport.scrollTop + ev.deltaY;\n if (ev.cancelable) {\n ev.preventDefault();\n }\n }\n if (this.wheel === 'zoom') {\n if (ev.deltaY >= 100) {\n this.zoomOut(ev);\n return ev.preventDefault();\n } else if (ev.deltaY <= -100) {\n this.zoomIn(ev);\n return ev.preventDefault();\n }\n }\n },\n preloadImages: function(hiRes) {\n if ( hiRes === void 0 ) hiRes = false;\n\n var i, j, k, ref, ref1, ref2, ref3, src;\n for (i = j = ref = this.currentPage - 3, ref1 = this.currentPage + 3; (ref <= ref1 ? j <= ref1 : j >= ref1); i = ref <= ref1 ? ++j : --j) {\n this.pageUrlLoading(i); // this preloads image\n }\n if (hiRes) {\n for (i = k = ref2 = this.currentPage, ref3 = this.currentPage + this.displayedPages; (ref2 <= ref3 ? k < ref3 : k > ref3); i = ref2 <= ref3 ? ++k : --k) {\n src = this.pagesHiRes[i];\n if (src) {\n (new Image()).src = src;\n }\n }\n }\n },\n goToPage: function(p) {\n if (p === null || p === this.page) {\n return;\n }\n if (this.pages[0] === null) {\n if (this.displayedPages === 2 && p === 1) {\n this.currentPage = 0;\n } else {\n this.currentPage = p;\n }\n } else {\n this.currentPage = p - 1;\n }\n this.minX = 2e308;\n this.maxX = -2e308;\n return this.currentCenterOffset = this.centerOffset;\n },\n loadImage: function(url) {\n var this$1$1 = this;\n\n var img;\n if (this.imageWidth === null) {\n // First loaded image defines the image width and height.\n // So it must be true image, not 'loading' image.\n return url;\n } else {\n if (this.loadedImages[url]) {\n return url;\n } else {\n img = new Image();\n img.onload = function () {\n if (this$1$1.$set) {\n return this$1$1.$set(this$1$1.loadedImages, url, true);\n } else {\n return this$1$1.loadedImages[url] = true;\n }\n };\n img.src = url;\n return this.loadingImage;\n }\n }\n }\n },\n watch: {\n currentPage: function() {\n this.firstPage = this.currentPage;\n this.secondPage = this.currentPage + 1;\n return this.preloadImages();\n },\n centerOffset: function() {\n var this$1$1 = this;\n\n var animate;\n if (this.animatingCenter) {\n return;\n }\n animate = function () {\n return requestAnimationFrame(function () {\n var diff, rate;\n rate = 0.1;\n diff = this$1$1.centerOffset - this$1$1.currentCenterOffset;\n if (Math.abs(diff) < 0.5) {\n this$1$1.currentCenterOffset = this$1$1.centerOffset;\n return this$1$1.animatingCenter = false;\n } else {\n this$1$1.currentCenterOffset += diff * rate;\n return animate();\n }\n });\n };\n this.animatingCenter = true;\n return animate();\n },\n scrollLeftLimited: function(val) {\n var this$1$1 = this;\n\n if (this.IE) {\n return requestAnimationFrame(function () {\n return this$1$1.$refs.viewport.scrollLeft = val;\n });\n } else {\n return this.$refs.viewport.scrollLeft = val;\n }\n },\n scrollTopLimited: function(val) {\n var this$1$1 = this;\n\n if (this.IE) {\n return requestAnimationFrame(function () {\n return this$1$1.$refs.viewport.scrollTop = val;\n });\n } else {\n return this.$refs.viewport.scrollTop = val;\n }\n },\n pages: function(after, before) {\n this.fixFirstPage();\n if (!(before != null ? before.length : void 0) && (after != null ? after.length : void 0)) {\n if (this.startPage > 1 && after[0] === null) {\n return this.currentPage++;\n }\n }\n },\n startPage: function(p) {\n return this.goToPage(p);\n }\n }\n};\n\nfunction normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {\r\n if (typeof shadowMode !== 'boolean') {\r\n createInjectorSSR = createInjector;\r\n createInjector = shadowMode;\r\n shadowMode = false;\r\n }\r\n // Vue.extend constructor export interop.\r\n var options = typeof script === 'function' ? script.options : script;\r\n // render functions\r\n if (template && template.render) {\r\n options.render = template.render;\r\n options.staticRenderFns = template.staticRenderFns;\r\n options._compiled = true;\r\n // functional template\r\n if (isFunctionalTemplate) {\r\n options.functional = true;\r\n }\r\n }\r\n // scopedId\r\n if (scopeId) {\r\n options._scopeId = scopeId;\r\n }\r\n var hook;\r\n if (moduleIdentifier) {\r\n // server build\r\n hook = function (context) {\r\n // 2.3 injection\r\n context =\r\n context || // cached call\r\n (this.$vnode && this.$vnode.ssrContext) || // stateful\r\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional\r\n // 2.2 with runInNewContext: true\r\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\r\n context = __VUE_SSR_CONTEXT__;\r\n }\r\n // inject component styles\r\n if (style) {\r\n style.call(this, createInjectorSSR(context));\r\n }\r\n // register component module identifier for async chunk inference\r\n if (context && context._registeredComponents) {\r\n context._registeredComponents.add(moduleIdentifier);\r\n }\r\n };\r\n // used by ssr in case component is cached and beforeCreate\r\n // never gets called\r\n options._ssrRegister = hook;\r\n }\r\n else if (style) {\r\n hook = shadowMode\r\n ? function (context) {\r\n style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));\r\n }\r\n : function (context) {\r\n style.call(this, createInjector(context));\r\n };\r\n }\r\n if (hook) {\r\n if (options.functional) {\r\n // register for functional component in vue file\r\n var originalRender = options.render;\r\n options.render = function renderWithStyleInjection(h, context) {\r\n hook.call(context);\r\n return originalRender(h, context);\r\n };\r\n }\r\n else {\r\n // inject component registration as beforeCreate hook\r\n var existing = options.beforeCreate;\r\n options.beforeCreate = existing ? [].concat(existing, hook) : [hook];\r\n }\r\n }\r\n return script;\r\n}\n\nvar isOldIE = typeof navigator !== 'undefined' &&\r\n /msie [6-9]\\\\b/.test(navigator.userAgent.toLowerCase());\r\nfunction createInjector(context) {\r\n return function (id, style) { return addStyle(id, style); };\r\n}\r\nvar HEAD;\r\nvar styles = {};\r\nfunction addStyle(id, css) {\r\n var group = isOldIE ? css.media || 'default' : id;\r\n var style = styles[group] || (styles[group] = { ids: new Set(), styles: [] });\r\n if (!style.ids.has(id)) {\r\n style.ids.add(id);\r\n var code = css.source;\r\n if (css.map) {\r\n // https://developer.chrome.com/devtools/docs/javascript-debugging\r\n // this makes source maps inside style tags work properly in Chrome\r\n code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */';\r\n // http://stackoverflow.com/a/26603875\r\n code +=\r\n '\\n/*# sourceMappingURL=data:application/json;base64,' +\r\n btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +\r\n ' */';\r\n }\r\n if (!style.element) {\r\n style.element = document.createElement('style');\r\n style.element.type = 'text/css';\r\n if (css.media)\r\n { style.element.setAttribute('media', css.media); }\r\n if (HEAD === undefined) {\r\n HEAD = document.head || document.getElementsByTagName('head')[0];\r\n }\r\n HEAD.appendChild(style.element);\r\n }\r\n if ('styleSheet' in style.element) {\r\n style.styles.push(code);\r\n style.element.styleSheet.cssText = style.styles\r\n .filter(Boolean)\r\n .join('\\n');\r\n }\r\n else {\r\n var index = style.ids.size - 1;\r\n var textNode = document.createTextNode(code);\r\n var nodes = style.element.childNodes;\r\n if (nodes[index])\r\n { style.element.removeChild(nodes[index]); }\r\n if (nodes.length)\r\n { style.element.insertBefore(textNode, nodes[index]); }\r\n else\r\n { style.element.appendChild(textNode); }\r\n }\r\n }\r\n}\n\n/* script */\nvar __vue_script__ = script;\n\n/* template */\nvar __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_vm._t(\"default\",null,null,{\n canFlipLeft: _vm.canFlipLeft,\n canFlipRight: _vm.canFlipRight,\n canZoomIn: _vm.canZoomIn,\n canZoomOut: _vm.canZoomOut,\n page: _vm.page,\n numPages: _vm.numPages,\n flipLeft: _vm.flipLeft,\n flipRight: _vm.flipRight,\n zoomIn: _vm.zoomIn,\n zoomOut: _vm.zoomOut,\n }),_vm._v(\" \"),_c('div',{ref:\"viewport\",staticClass:\"viewport\",class:{\n zoom: _vm.zooming || _vm.zoom > 1,\n 'drag-to-scroll': _vm.dragToScroll,\n },style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{\"touchmove\":_vm.onTouchMove,\"pointermove\":_vm.onPointerMove,\"mousemove\":_vm.onMouseMove,\"touchend\":_vm.onTouchEnd,\"touchcancel\":_vm.onTouchEnd,\"pointerup\":_vm.onPointerUp,\"pointercancel\":_vm.onPointerUp,\"mouseup\":_vm.onMouseUp,\"wheel\":_vm.onWheel}},[_c('div',{staticClass:\"flipbook-container\",style:({ transform: (\"scale(\" + _vm.zoom + \")\") })},[_c('div',{staticClass:\"click-to-flip left\",style:({ cursor: _vm.canFlipLeft ? 'pointer' : 'auto' }),on:{\"click\":_vm.flipLeft}}),_vm._v(\" \"),_c('div',{staticClass:\"click-to-flip right\",style:({ cursor: _vm.canFlipRight ? 'pointer' : 'auto' }),on:{\"click\":_vm.flipRight}}),_vm._v(\" \"),_c('div',{style:({ transform: (\"translateX(\" + _vm.centerOffsetSmoothed + \"px)\") })},[(_vm.showLeftPage)?_c('img',{staticClass:\"page fixed\",style:({\n width: _vm.pageWidth + 'px',\n height: _vm.pageHeight + 'px',\n left: _vm.xMargin + 'px',\n top: _vm.yMargin + 'px',\n }),attrs:{\"src\":_vm.pageUrlLoading(_vm.leftPage, true)},on:{\"load\":function($event){return _vm.didLoadImage($event)}}}):_vm._e(),_vm._v(\" \"),(_vm.showRightPage)?_c('img',{staticClass:\"page fixed\",style:({\n width: _vm.pageWidth + 'px',\n height: _vm.pageHeight + 'px',\n left: _vm.viewWidth / 2 + 'px',\n top: _vm.yMargin + 'px',\n }),attrs:{\"src\":_vm.pageUrlLoading(_vm.rightPage, true)},on:{\"load\":function($event){return _vm.didLoadImage($event)}}}):_vm._e(),_vm._v(\" \"),_c('div',{style:({ opacity: _vm.flip.opacity })},_vm._l((_vm.polygonArray),function(ref){\n var key = ref[0];\n var bgImage = ref[1];\n var lighting = ref[2];\n var bgPos = ref[3];\n var transform = ref[4];\n var z = ref[5];\nreturn _c('div',{key:key,staticClass:\"polygon\",class:{ blank: !bgImage },style:({\n backgroundImage: bgImage && (\"url(\" + (_vm.loadImage(bgImage)) + \")\"),\n backgroundSize: _vm.polygonBgSize,\n backgroundPosition: bgPos,\n width: _vm.polygonWidth,\n height: _vm.polygonHeight,\n transform: transform,\n zIndex: z,\n })},[_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(lighting.length),expression:\"lighting.length\"}],staticClass:\"lighting\",style:({ backgroundImage: lighting })})])}),0),_vm._v(\" \"),_c('div',{staticClass:\"bounding-box\",style:({\n left: _vm.boundingLeft + 'px',\n top: _vm.yMargin + 'px',\n width: _vm.boundingRight - _vm.boundingLeft + 'px',\n height: _vm.pageHeight + 'px',\n cursor: _vm.cursor,\n }),on:{\"touchstart\":_vm.onTouchStart,\"pointerdown\":_vm.onPointerDown,\"mousedown\":_vm.onMouseDown}})])])])],2)};\nvar __vue_staticRenderFns__ = [];\n\n /* style */\n var __vue_inject_styles__ = function (inject) {\n if (!inject) { return }\n inject(\"data-v-e3f0fbe2_0\", { source: \".viewport[data-v-e3f0fbe2]{-webkit-overflow-scrolling:touch;width:100%;height:100%}.viewport.zoom[data-v-e3f0fbe2]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-e3f0fbe2]{overflow:hidden}.flipbook-container[data-v-e3f0fbe2]{position:relative;width:100%;height:100%;transform-origin:top left;user-select:none}.click-to-flip[data-v-e3f0fbe2]{position:absolute;width:50%;height:100%;top:0;user-select:none}.click-to-flip.left[data-v-e3f0fbe2]{left:0}.click-to-flip.right[data-v-e3f0fbe2]{right:0}.bounding-box[data-v-e3f0fbe2]{position:absolute;user-select:none}.page[data-v-e3f0fbe2]{position:absolute;backface-visibility:hidden}.polygon[data-v-e3f0fbe2]{position:absolute;top:0;left:0;background-repeat:no-repeat;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-e3f0fbe2]{background-color:#ddd}.polygon .lighting[data-v-e3f0fbe2]{width:100%;height:100%}\", map: undefined, media: undefined });\n\n };\n /* scoped */\n var __vue_scope_id__ = \"data-v-e3f0fbe2\";\n /* module identifier */\n var __vue_module_identifier__ = undefined;\n /* functional template */\n var __vue_is_functional_template__ = false;\n /* style inject SSR */\n \n /* style inject shadow dom */\n \n\n \n var __vue_component__ = /*#__PURE__*/normalizeComponent(\n { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },\n __vue_inject_styles__,\n __vue_script__,\n __vue_scope_id__,\n __vue_is_functional_template__,\n __vue_module_identifier__,\n false,\n createInjector,\n undefined,\n undefined\n );\n\nexport { __vue_component__ as default };\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"catalogue-drawer-container\"},[(_vm.visible)?_c('div',{staticClass:\"drawer-overlay\",on:{\"click\":_vm.closeDrawer}}):_vm._e(),_c('div',{staticClass:\"catalogue-drawer\",class:{ 'drawer-open': _vm.visible },style:({ width: _vm.drawerWidth })},[_c('div',{staticClass:\"drawer-header\"},[_c('h3',{staticClass:\"drawer-title\"},[_vm._v(\"目录\")]),_c('button',{staticClass:\"close-button\",on:{\"click\":_vm.closeDrawer}},[_c('span',{staticClass:\"close-icon\"},[_vm._v(\"×\")])])]),_c('div',{staticClass:\"page-jump-section\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.jumpPageInput),expression:\"jumpPageInput\"}],staticClass:\"page-jump-input\",attrs:{\"type\":\"text\",\"placeholder\":\"请输入页码\"},domProps:{\"value\":(_vm.jumpPageInput)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.jumpPageInput=$event.target.value},_vm.handlePageInput],\"focus\":_vm.onInputFocus,\"blur\":_vm.onInputBlur}}),_c('button',{staticClass:\"btn-jump\",on:{\"click\":_vm.handleJumpToPage}},[_vm._v(\" 跳转 \")])]),_c('div',{staticClass:\"drawer-content\"},[(_vm.loading)?_c('div',{staticClass:\"loading-container\"},[_c('div',{staticClass:\"loading-spinner\"}),_c('p',[_vm._v(\"加载目录中...\")])]):(_vm.error)?_c('div',{staticClass:\"error-container\"},[_c('div',{staticClass:\"error-icon\"},[_vm._v(\"⚠️\")]),_c('p',[_vm._v(\"目录加载失败\")]),_c('button',{staticClass:\"retry-button\",on:{\"click\":_vm.fetchCatalogue}},[_vm._v(\" 重新加载 \")])]):(_vm.catalogueData && _vm.catalogueData.length > 0)?_c('div',{staticClass:\"catalogue-list\"},_vm._l((_vm.catalogueData),function(item,index){return _c('catalogue-item',{key:`catalogue-${index}`,attrs:{\"item\":item || {},\"level\":0},on:{\"item-click\":_vm.onItemClick}})}),1):_c('div',{staticClass:\"empty-container\"},[_c('p',[_vm._v(\"暂无目录\")])])])])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","<template>\r\n <div class=\"catalogue-drawer-container\">\r\n <!-- 遮罩层 -->\r\n <div v-if=\"visible\" class=\"drawer-overlay\" @click=\"closeDrawer\"></div>\r\n \r\n <!-- 抽屉主体 -->\r\n <div \r\n class=\"catalogue-drawer\" \r\n :class=\"{ 'drawer-open': visible }\"\r\n :style=\"{ width: drawerWidth }\"\r\n >\r\n <div class=\"drawer-header\">\r\n <h3 class=\"drawer-title\">目录</h3>\r\n <button class=\"close-button\" @click=\"closeDrawer\">\r\n <span class=\"close-icon\">×</span>\r\n </button>\r\n </div>\r\n\r\n <!-- 页码跳转 -->\r\n <div class=\"page-jump-section\">\r\n <input\r\n type=\"text\"\r\n v-model=\"jumpPageInput\"\r\n @input=\"handlePageInput\"\r\n placeholder=\"请输入页码\"\r\n class=\"page-jump-input\"\r\n @focus=\"onInputFocus\"\r\n @blur=\"onInputBlur\"\r\n />\r\n <button @click=\"handleJumpToPage\" class=\"btn-jump\">\r\n 跳转\r\n </button>\r\n </div>\r\n\r\n <div class=\"drawer-content\">\r\n <!-- 加载状态 -->\r\n <div v-if=\"loading\" class=\"loading-container\">\r\n <div class=\"loading-spinner\"></div>\r\n <p>加载目录中...</p>\r\n </div>\r\n \r\n <!-- 错误状态 -->\r\n <div v-else-if=\"error\" class=\"error-container\">\r\n <div class=\"error-icon\">⚠️</div>\r\n <p>目录加载失败</p>\r\n <button class=\"retry-button\" @click=\"fetchCatalogue\">\r\n 重新加载\r\n </button>\r\n </div>\r\n \r\n <!-- 目录内容 -->\r\n <div v-else-if=\"catalogueData && catalogueData.length > 0\" class=\"catalogue-list\">\r\n <catalogue-item\r\n v-for=\"(item, index) in catalogueData\"\r\n :key=\"`catalogue-${index}`\"\r\n :item=\"item || {}\"\r\n :level=\"0\"\r\n @item-click=\"onItemClick\"\r\n />\r\n </div>\r\n \r\n <!-- 空状态 -->\r\n <div v-else class=\"empty-container\">\r\n <p>暂无目录</p>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script>\r\n// 目录项组件\r\nconst CatalogueItem = {\r\n name: 'CatalogueItem',\r\n props: {\r\n item: {\r\n type: Object,\r\n required: true\r\n },\r\n level: {\r\n type: Number,\r\n default: 0\r\n }\r\n },\r\n data() {\r\n return {\r\n expanded: false\r\n }\r\n },\r\n computed: {\r\n hasChildren() {\r\n const children = this.item.childrenList || this.item.children || this.item.subItems || this.item.items\r\n return children && children.length > 0\r\n },\r\n childrenData() {\r\n return this.item.childrenList || this.item.children || this.item.subItems || this.item.items || []\r\n },\r\n itemStyle() {\r\n // 移除缩进,所有层级都左对齐\r\n return {\r\n paddingLeft: '12px',\r\n paddingRight: '12px'\r\n }\r\n },\r\n levelClass() {\r\n return `level-${this.level}`\r\n }\r\n },\r\n methods: {\r\n toggleExpand() {\r\n if (this.hasChildren) {\r\n this.expanded = !this.expanded\r\n } else {\r\n this.handleClick()\r\n }\r\n },\r\n handleClick() {\r\n this.$emit('item-click', this.item)\r\n }\r\n },\r\n render(h) {\r\n const item = this.item\r\n const titleText = item.titleName || item.title || item.name || item.text || item.label || '未命名章节'\r\n const pageNum = item.startPageNum || item.pageNumber || item.page || item.pageNum\r\n \r\n // 创建拖拽手柄\r\n const dragHandle = h('span', {\r\n class: 'drag-handle'\r\n }, '')\r\n \r\n // 创建展开图标\r\n const expandIcon = this.hasChildren ? h('span', {\r\n class: 'expand-icon'\r\n }, this.expanded ? '▼' : '▶') : null\r\n \r\n // 创建层级指示器\r\n const levelIndicator = this.level > 0 ? h('span', {\r\n class: `level-indicator level-${this.level}`\r\n }, '•'.repeat(this.level)) : null\r\n \r\n // 创建标题\r\n const titleSpan = h('span', {\r\n class: 'item-title'\r\n }, titleText)\r\n \r\n // 创建页码(放在标题后面)\r\n // const pageSpan = pageNum ? h('span', {\r\n // class: 'page-number'\r\n // }, `第${pageNum}页`) : null\r\n const pageSpan = null\r\n // 创建子级目录\r\n const childrenDiv = (this.hasChildren && this.expanded) ? h('div', {\r\n class: 'children-container'\r\n }, this.childrenData.map((child, index) => {\r\n return h('catalogue-item', {\r\n key: index,\r\n props: {\r\n item: child,\r\n level: this.level + 1\r\n },\r\n on: {\r\n 'item-click': (item) => this.$emit('item-click', item)\r\n }\r\n })\r\n })) : null\r\n \r\n return h('div', {\r\n class: 'catalogue-item-wrapper'\r\n }, [\r\n h('div', {\r\n class: [\r\n 'catalogue-item',\r\n this.levelClass,\r\n {\r\n 'has-children': this.hasChildren,\r\n 'expanded': this.expanded\r\n }\r\n ],\r\n style: this.itemStyle,\r\n on: {\r\n click: this.toggleExpand\r\n }\r\n }, [\r\n h('div', {\r\n class: 'item-content'\r\n }, [dragHandle, expandIcon, levelIndicator, titleSpan, pageSpan].filter(Boolean))\r\n ]),\r\n childrenDiv\r\n ].filter(Boolean))\r\n }\r\n}\r\n\r\nexport default {\r\n name: 'BookCatalogueDrawer',\r\n components: {\r\n CatalogueItem\r\n },\r\n props: {\r\n value: {\r\n type: Boolean,\r\n default: false\r\n },\r\n bookId: {\r\n type: String,\r\n default: ''\r\n },\r\n catalogue: {\r\n type: Array,\r\n default: () => []\r\n },\r\n loading: {\r\n type: Boolean,\r\n default: false\r\n }\r\n },\r\n data() {\r\n return {\r\n catalogueData: [],\r\n error: null,\r\n jumpPageInput: ''\r\n }\r\n },\r\n computed: {\r\n visible: {\r\n get() {\r\n return this.value\r\n },\r\n set(val) {\r\n this.$emit('input', val)\r\n }\r\n },\r\n drawerWidth() {\r\n // 响应式宽度\r\n return window.innerWidth <= 768 ? '80%' : '400px'\r\n }\r\n },\r\n watch: {\r\n visible(newVal) {\r\n if (newVal && this.bookId && !this.catalogueData.length) {\r\n this.$emit('fetch-catalogue', this.bookId)\r\n }\r\n },\r\n bookId(newVal) {\r\n if (newVal && this.visible) {\r\n this.$emit('fetch-catalogue', newVal)\r\n }\r\n },\r\n catalogue: {\r\n handler(newVal) {\r\n if (newVal && newVal.length > 0) {\r\n this.catalogueData = this.processCatalogueData(newVal)\r\n }\r\n },\r\n immediate: true\r\n }\r\n },\r\n methods: {\r\n /**\r\n * 获取目录数据 - 触发事件让父组件处理\r\n */\r\n fetchCatalogue() {\r\n if (!this.bookId) {\r\n console.warn('缺少 bookId,无法获取目录')\r\n return\r\n }\r\n this.$emit('fetch-catalogue', this.bookId)\r\n },\r\n\r\n /**\r\n * 处理目录数据,确保数据结构正确\r\n * @param {Array} data - 原始目录数据\r\n * @returns {Array} 处理后的目录数据\r\n */\r\n processCatalogueData(data) {\r\n if (!Array.isArray(data)) {\r\n return []\r\n }\r\n \r\n return data.map(item => {\r\n if (!item || typeof item !== 'object') {\r\n return {\r\n titleName: '未知章节',\r\n startPageNum: null,\r\n childrenList: []\r\n }\r\n }\r\n \r\n // 确保必要的字段存在\r\n const processedItem = {\r\n ...item,\r\n titleName: item.titleName || item.title || item.name || '未命名章节',\r\n startPageNum: item.startPageNum || item.pageNumber || item.page || item.pageNum || null,\r\n childrenList: item.childrenList || item.children || item.subItems || item.items || []\r\n }\r\n \r\n // 递归处理子级\r\n if (processedItem.childrenList && processedItem.childrenList.length > 0) {\r\n processedItem.childrenList = this.processCatalogueData(processedItem.childrenList)\r\n }\r\n \r\n return processedItem\r\n })\r\n },\r\n\r\n /**\r\n * 目录项点击事件\r\n * @param {Object} item - 点击的目录项\r\n */\r\n onItemClick(item) {\r\n console.log('目录项被点击:', item)\r\n \r\n // 发送事件给父组件\r\n this.$emit('catalogue-click', item)\r\n \r\n // 如果有页码信息,可以进行跳转\r\n const pageNumber = item.startPageNum || item.pageNumber || item.page || item.pageNum\r\n if (pageNumber) {\r\n this.$emit('page-jump', pageNumber)\r\n }\r\n },\r\n\r\n /**\r\n * 关闭抽屉\r\n */\r\n closeDrawer() {\r\n this.$emit('input', false)\r\n },\r\n\r\n /**\r\n * 处理页码输入\r\n */\r\n handlePageInput(e) {\r\n const value = e.target.value\r\n const numValue = value.replace(/[^\\d]/g, '')\r\n this.jumpPageInput = numValue\r\n },\r\n\r\n /**\r\n * 执行页码跳转\r\n */\r\n handleJumpToPage() {\r\n const pageNum = parseInt(this.jumpPageInput)\r\n if (pageNum && pageNum >= 1) {\r\n this.$emit('page-jump', pageNum)\r\n this.jumpPageInput = ''\r\n this.closeDrawer()\r\n }\r\n },\r\n\r\n /**\r\n * 输入框获得焦点\r\n */\r\n onInputFocus() {\r\n // 可以在这里添加焦点处理逻辑\r\n },\r\n\r\n /**\r\n * 输入框失去焦点\r\n */\r\n onInputBlur() {\r\n this.$emit('Focus')\r\n },\r\n\r\n /**\r\n * 重置数据\r\n */\r\n resetData() {\r\n this.catalogueData = []\r\n this.error = null\r\n this.loading = false\r\n },\r\n\r\n /**\r\n * 处理窗口大小变化\r\n */\r\n handleResize() {\r\n // 可以在这里添加响应式逻辑\r\n }\r\n },\r\n\r\n mounted() {\r\n // 监听窗口大小变化\r\n window.addEventListener('resize', this.handleResize)\r\n },\r\n\r\n beforeDestroy() {\r\n window.removeEventListener('resize', this.handleResize)\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n/* 容器 */\r\n.catalogue-drawer-container {\r\n position: relative;\r\n}\r\n\r\n/* 遮罩层 */\r\n.drawer-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(0, 0, 0, 0.5);\r\n z-index: 19998;\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n/* 抽屉主体 */\r\n.catalogue-drawer {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n height: 100%;\r\n background: #ffffff;\r\n box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);\r\n transform: translateX(-100%);\r\n transition: transform 0.3s ease;\r\n z-index: 19999;\r\n overflow: hidden;\r\n /* 处理安全区域 */\r\n padding-top: env(safe-area-inset-top);\r\n padding-bottom: env(safe-area-inset-bottom);\r\n}\r\n\r\n.catalogue-drawer.drawer-open {\r\n transform: translateX(0);\r\n}\r\n\r\n.drawer-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px 20px;\r\n border-bottom: 1px solid #e5e5e5;\r\n background: #ffffff;\r\n position: sticky;\r\n top: 0;\r\n z-index: 100;\r\n}\r\n\r\n.drawer-title {\r\n font-size: 18px;\r\n font-weight: 600;\r\n color: #333333;\r\n margin: 0;\r\n}\r\n\r\n.close-button {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n padding: 6px;\r\n border-radius: 4px;\r\n transition: background-color 0.2s ease;\r\n width: 32px;\r\n height: 32px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.close-button:hover {\r\n background: #f5f5f5;\r\n}\r\n\r\n.close-icon {\r\n font-size: 18px;\r\n color: #666;\r\n line-height: 1;\r\n display: block;\r\n}\r\n\r\n.close-button:hover .close-icon {\r\n color: #333;\r\n}\r\n\r\n/* 页码跳转区域 */\r\n.page-jump-section {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 12px 16px;\r\n border-bottom: 1px solid #e5e5e5;\r\n background: #f8f9fa;\r\n}\r\n\r\n.page-jump-input {\r\n flex: 1;\r\n min-width: 0;\r\n padding: 10px 12px;\r\n border: 1px solid #ddd;\r\n border-radius: 6px;\r\n background: #fff;\r\n color: #333;\r\n font-size: 16px;\r\n text-align: center;\r\n outline: none;\r\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\r\n}\r\n\r\n.page-jump-input:focus {\r\n border-color: #1989fa;\r\n box-shadow: 0 0 0 2px rgba(25, 137, 250, 0.1);\r\n}\r\n\r\n.page-jump-input::placeholder {\r\n color: #999;\r\n font-size: 14px;\r\n}\r\n\r\n.btn-jump {\r\n flex-shrink: 0;\r\n padding: 10px 20px;\r\n background: #1989fa;\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 14px;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: background 0.2s ease;\r\n white-space: nowrap;\r\n}\r\n\r\n.btn-jump:hover {\r\n background: #0066cc;\r\n}\r\n\r\n.btn-jump:active {\r\n background: #004499;\r\n}\r\n\r\n.drawer-content {\r\n height: calc(100% - 65px);\r\n overflow-y: auto;\r\n -webkit-overflow-scrolling: touch;\r\n}\r\n\r\n.loading-container {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n height: 200px;\r\n flex-direction: column;\r\n gap: 12px;\r\n}\r\n\r\n.loading-spinner {\r\n width: 40px;\r\n height: 40px;\r\n border: 3px solid #f3f3f3;\r\n border-top: 3px solid #1989fa;\r\n border-radius: 50%;\r\n animation: spin 1s linear infinite;\r\n}\r\n\r\n@keyframes spin {\r\n 0% { transform: rotate(0deg); }\r\n 100% { transform: rotate(360deg); }\r\n}\r\n\r\n.error-container,\r\n.empty-container {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 40px 20px;\r\n text-align: left;\r\n}\r\n\r\n.error-icon {\r\n font-size: 48px;\r\n margin-bottom: 16px;\r\n}\r\n\r\n.retry-button {\r\n margin-top: 16px;\r\n background: #1989fa;\r\n color: white;\r\n border: none;\r\n padding: 8px 16px;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n transition: background-color 0.2s ease;\r\n}\r\n\r\n.retry-button:hover {\r\n background: #0066cc;\r\n}\r\n\r\n.catalogue-list {\r\n padding: 0;\r\n}\r\n\r\n.catalogue-item-wrapper {\r\n border-bottom: 1px solid #f0f0f0;\r\n}\r\n\r\n.catalogue-item-wrapper:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.catalogue-item {\r\n padding: 12px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n position: relative;\r\n min-height: 44px;\r\n border-left: 3px solid transparent;\r\n}\r\n\r\n.catalogue-item:hover {\r\n background-color: #f8f9fa;\r\n}\r\n\r\n.catalogue-item:active {\r\n background-color: #e9ecef;\r\n}\r\n\r\n.item-content {\r\n display: flex;\r\n align-items: center;\r\n flex: 1;\r\n width: 100%;\r\n justify-content: flex-start;\r\n gap: 8px;\r\n}\r\n\r\n/* 拖拽手柄样式 */\r\n.drag-handle {\r\n color: #ccc;\r\n font-size: 14px;\r\n cursor: grab;\r\n transition: color 0.2s ease;\r\n flex-shrink: 0;\r\n width: 20px;\r\n text-align: center;\r\n line-height: 1;\r\n user-select: none;\r\n}\r\n\r\n.drag-handle:hover {\r\n color: #999;\r\n}\r\n\r\n.drag-handle:active {\r\n cursor: grabbing;\r\n color: #666;\r\n}\r\n\r\n/* 层级指示器样式 */\r\n.level-indicator {\r\n font-size: 10px;\r\n color: #999;\r\n flex-shrink: 0;\r\n line-height: 1;\r\n margin-right: 4px;\r\n}\r\n\r\n.level-indicator.level-1 {\r\n color: #007bff;\r\n}\r\n\r\n.level-indicator.level-2 {\r\n color: #28a745;\r\n}\r\n\r\n.level-indicator.level-3 {\r\n color: #ffc107;\r\n}\r\n\r\n.expand-icon {\r\n font-size: 12px;\r\n color: #999;\r\n transition: color 0.2s ease;\r\n flex-shrink: 0;\r\n width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.catalogue-item:hover .expand-icon {\r\n color: #666;\r\n}\r\n\r\n.item-title {\r\n font-size: 14px;\r\n color: #333;\r\n line-height: 1.4;\r\n font-weight: 400;\r\n}\r\n\r\n.page-number {\r\n font-size: 12px;\r\n color: #999;\r\n margin-left: 6px;\r\n}\r\n\r\n/* 不同层级的样式 - 用颜色和字体大小区分层级 */\r\n.catalogue-item.level-0 {\r\n background: linear-gradient(90deg, transparent, rgba(0, 123, 255, 0.05), transparent);\r\n}\r\n\r\n.catalogue-item.level-0 .item-title {\r\n font-size: 15px;\r\n color: #333;\r\n font-weight: 600;\r\n}\r\n\r\n.catalogue-item.level-1 {\r\n background: linear-gradient(90deg, transparent, rgba(40, 167, 69, 0.03), transparent);\r\n}\r\n\r\n.catalogue-item.level-1 .item-title {\r\n font-size: 14px;\r\n color: #555;\r\n font-weight: 500;\r\n}\r\n\r\n.catalogue-item.level-2 {\r\n background: linear-gradient(90deg, transparent, rgba(255, 193, 7, 0.03), transparent);\r\n}\r\n\r\n.catalogue-item.level-2 .item-title {\r\n font-size: 13px;\r\n color: #666;\r\n font-weight: 400;\r\n}\r\n\r\n.catalogue-item.level-3 .item-title {\r\n font-size: 12px;\r\n color: #777;\r\n font-weight: 400;\r\n}\r\n\r\n.children-container {\r\n /* 移除背景色,保持一致性 */\r\n background: transparent;\r\n}\r\n\r\n/* 移动端适配 */\r\n@media (max-width: 768px) {\r\n .catalogue-drawer {\r\n /* 移动端安全区域适配 */\r\n padding-top: max(env(safe-area-inset-top), 20px);\r\n padding-bottom: max(env(safe-area-inset-bottom), 20px);\r\n }\r\n \r\n .drawer-header {\r\n padding: 14px 16px;\r\n }\r\n \r\n .drawer-title {\r\n font-size: 16px;\r\n }\r\n \r\n .catalogue-item {\r\n padding: 10px 8px;\r\n min-height: 44px; /* 增加触摸区域 */\r\n }\r\n \r\n .drag-handle {\r\n font-size: 16px; /* 移动端增大手柄 */\r\n width: 24px;\r\n }\r\n \r\n .item-title {\r\n font-size: 13px;\r\n }\r\n \r\n .catalogue-item.level-0 .item-title {\r\n font-size: 14px;\r\n }\r\n \r\n .page-number {\r\n font-size: 11px;\r\n }\r\n \r\n .level-indicator {\r\n font-size: 12px; /* 移动端增大指示器 */\r\n }\r\n}\r\n\r\n/* 滚动条样式 */\r\n.drawer-content::-webkit-scrollbar {\r\n width: 4px;\r\n}\r\n\r\n.drawer-content::-webkit-scrollbar-track {\r\n background: #f5f5f5;\r\n}\r\n\r\n.drawer-content::-webkit-scrollbar-thumb {\r\n background: #ccc;\r\n border-radius: 2px;\r\n}\r\n\r\n.drawer-content::-webkit-scrollbar-thumb:hover {\r\n background: #999;\r\n}\r\n\r\n/* 拖拽状态样式 */\r\n.catalogue-item.dragging {\r\n opacity: 0.5;\r\n transform: scale(0.95);\r\n background: #f0f8ff;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n/* 拖拽悬停目标样式 */\r\n.catalogue-item.drag-over {\r\n border-top: 2px solid #007bff;\r\n background: rgba(0, 123, 255, 0.1);\r\n}\r\n\r\n/* 增强视觉层次 */\r\n.catalogue-item {\r\n border-left: 3px solid transparent;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.catalogue-item.level-0 {\r\n border-left-color: #007bff;\r\n}\r\n\r\n.catalogue-item.level-1 {\r\n border-left-color: #28a745;\r\n}\r\n\r\n.catalogue-item.level-2 {\r\n border-left-color: #ffc107;\r\n}\r\n\r\n.catalogue-item.level-3 {\r\n border-left-color: #dc3545;\r\n}\r\n</style>\r\n","import mod from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookCatalogueDrawer.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookCatalogueDrawer.vue?vue&type=script&lang=js\"","// extracted by mini-css-extract-plugin\nexport {};","export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../node_modules/@vue/vue-loader-v15/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookCatalogueDrawer.vue?vue&type=style&index=0&id=3930be62&prod&scoped=true&lang=css\"","/* globals __VUE_SSR_CONTEXT__ */\n\n// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).\n// This module is a runtime utility for cleaner component module output and will\n// be included in the final webpack user bundle.\n\nexport default function normalizeComponent(\n scriptExports,\n render,\n staticRenderFns,\n functionalTemplate,\n injectStyles,\n scopeId,\n moduleIdentifier /* server only */,\n shadowMode /* vue-cli only */\n) {\n // Vue.extend constructor export interop\n var options =\n typeof scriptExports === 'function' ? scriptExports.options : scriptExports\n\n // render functions\n if (render) {\n options.render = render\n options.staticRenderFns = staticRenderFns\n options._compiled = true\n }\n\n // functional template\n if (functionalTemplate) {\n options.functional = true\n }\n\n // scopedId\n if (scopeId) {\n options._scopeId = 'data-v-' + scopeId\n }\n\n var hook\n if (moduleIdentifier) {\n // server build\n hook = function (context) {\n // 2.3 injection\n context =\n context || // cached call\n (this.$vnode && this.$vnode.ssrContext) || // stateful\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional\n // 2.2 with runInNewContext: true\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\n context = __VUE_SSR_CONTEXT__\n }\n // inject component styles\n if (injectStyles) {\n injectStyles.call(this, context)\n }\n // register component module identifier for async chunk inferrence\n if (context && context._registeredComponents) {\n context._registeredComponents.add(moduleIdentifier)\n }\n }\n // used by ssr in case component is cached and beforeCreate\n // never gets called\n options._ssrRegister = hook\n } else if (injectStyles) {\n hook = shadowMode\n ? function () {\n injectStyles.call(\n this,\n (options.functional ? this.parent : this).$root.$options.shadowRoot\n )\n }\n : injectStyles\n }\n\n if (hook) {\n if (options.functional) {\n // for template-only hot-reload because in that case the render fn doesn't\n // go through the normalizer\n options._injectStyles = hook\n // register for functional component in vue file\n var originalRender = options.render\n options.render = function renderWithStyleInjection(h, context) {\n hook.call(context)\n return originalRender(h, context)\n }\n } else {\n // inject component registration as beforeCreate hook\n var existing = options.beforeCreate\n options.beforeCreate = existing ? [].concat(existing, hook) : [hook]\n }\n }\n\n return {\n exports: scriptExports,\n options: options\n }\n}\n","import { render, staticRenderFns } from \"./BookCatalogueDrawer.vue?vue&type=template&id=3930be62&scoped=true\"\nimport script from \"./BookCatalogueDrawer.vue?vue&type=script&lang=js\"\nexport * from \"./BookCatalogueDrawer.vue?vue&type=script&lang=js\"\nimport style0 from \"./BookCatalogueDrawer.vue?vue&type=style&index=0&id=3930be62&prod&scoped=true&lang=css\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"3930be62\",\n null\n \n)\n\nexport default component.exports","<template>\r\n <div\r\n class=\"photo-album-container\"\r\n @touchstart=\"onContainerTouchStart\"\r\n @touchmove=\"onContainerTouchMove\"\r\n @touchend=\"onContainerTouchEnd\"\r\n @dblclick=\"onContainerDoubleClick\"\r\n @click=\"onContentClick\"\r\n :style=\"{ transform: `scale(${zoomScale}) translate(${translateX}px, ${translateY}px)` }\"\r\n >\r\n <div class=\"album-header\" v-if=\"false\">\r\n <h1>氪氪</h1>\r\n <!-- <p>双页模式 - 双击图片放大,滚轮缩放,拖拽翻页</p> -->\r\n </div>\r\n \r\n <div\r\n v-if=\"contentReady\"\r\n class=\"flipbook-wrapper\"\r\n @dblclick=\"onFlipbookDoubleClick\"\r\n @touchstart.capture=\"onFlipbookTouchStart\"\r\n @touchend.capture=\"onFlipbookTouchEnd\"\r\n >\r\n <!-- 仿真翻页模式 -->\r\n <flipbook\r\n v-if=\"flipMode === 'flip' && flag\"\r\n :class=\"['flipbook', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n :pages=\"pages\"\r\n :pagesHiRes=\"pagesHiRes\"\r\n :startPage=\"startPage\"\r\n v-slot=\"{ page, canFlipLeft, canFlipRight }\"\r\n ref=\"flipbook\"\r\n @flip-left-end=\"onFlipLeftEnd\"\r\n @flip-right-end=\"onFlipRightEnd\"\r\n :zooms=\"null\"\r\n :ambient-light=\"0.6\"\r\n :gloss=\"0.4\"\r\n :single-page=\"isMobile\"\r\n :click-to-flip=\"false\"\r\n :wheel-to-flip=\"!isMobile\"\r\n :swipe-to-flip=\"true\"\r\n :center-pages=\"true\"\r\n >\r\n </flipbook>\r\n \r\n <!-- 滑动翻页模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'slide'\"\r\n :class=\"['slide-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"slideViewer\"\r\n @touchstart=\"onSlideViewerTouchStart\"\r\n @touchmove=\"onSlideViewerTouchMove\"\r\n @touchend=\"onSlideViewerTouchEnd\"\r\n >\r\n <div class=\"slide-container\" :style=\"slideContainerStyle\">\r\n <div\r\n v-for=\"(page, index) in pages\"\r\n :key=\"index\"\r\n class=\"slide-page\"\r\n :class=\"{ 'slide-page-active': index + 1 === currentPage }\"\r\n >\r\n <img v-if=\"page\" :src=\"page\" alt=\"\" class=\"slide-page-image\" />\r\n <div v-else class=\"slide-page-placeholder\">封面/封底</div>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 淡入淡出翻页模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'fade'\"\r\n :class=\"['fade-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"fadeViewer\"\r\n >\r\n <transition name=\"fade-page\" mode=\"out-in\">\r\n <div :key=\"currentPage\" class=\"fade-page-container\">\r\n <img v-if=\"pages[currentPage - 1]\" :src=\"pages[currentPage - 1]\" alt=\"\" class=\"fade-page-image\" />\r\n <div v-else class=\"fade-page-placeholder\">封面/封底</div>\r\n </div>\r\n </transition>\r\n </div>\r\n \r\n <!-- 垂直滚动模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'scroll'\"\r\n :class=\"['scroll-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"scrollViewer\"\r\n @scroll=\"onScrollViewerScroll\"\r\n >\r\n <div class=\"scroll-container\">\r\n <div\r\n v-for=\"(page, index) in pages\"\r\n :key=\"index\"\r\n class=\"scroll-page\"\r\n :ref=\"'scrollPage' + index\"\r\n >\r\n <img v-if=\"page\" :src=\"page\" alt=\"\" class=\"scroll-page-image\" />\r\n <div v-else class=\"scroll-page-placeholder\">封面/封底</div>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 截断特效模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'clip'\"\r\n :class=\"['clip-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"clipViewer\"\r\n >\r\n <div class=\"clip-pages-wrapper\">\r\n <!-- 当前页 -->\r\n <transition name=\"clip-current\">\r\n <div :key=\"'current-' + currentPage\" class=\"clip-page clip-page-current\">\r\n <img v-if=\"pages[currentPage - 1]\" :src=\"pages[currentPage - 1]\" alt=\"\" class=\"clip-page-image\" />\r\n <div v-else class=\"clip-page-placeholder\">封面/封底</div>\r\n </div>\r\n </transition>\r\n <!-- 下一页预览(部分可见) -->\r\n <div v-if=\"currentPage < totalPages\" class=\"clip-page clip-page-next\">\r\n <img v-if=\"pages[currentPage]\" :src=\"pages[currentPage]\" alt=\"\" class=\"clip-page-image\" />\r\n <div v-else class=\"clip-page-placeholder\">封面/封底</div>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 卡片风格模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'card'\"\r\n :class=\"['card-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"cardViewer\"\r\n >\r\n <div class=\"card-stack\">\r\n <!-- 显示前后各一页的卡片堆叠效果 -->\r\n <transition-group name=\"card-flip\" tag=\"div\" class=\"card-transition-group\">\r\n <div\r\n v-for=\"(page, index) in visibleCards\"\r\n :key=\"'card-' + page.originalIndex\"\r\n class=\"card-item\"\r\n :class=\"{\r\n 'card-item-prev': page.position === 'prev',\r\n 'card-item-current': page.position === 'current',\r\n 'card-item-next': page.position === 'next'\r\n }\"\r\n :style=\"getCardStyle(page.position)\"\r\n >\r\n <div class=\"card-content\">\r\n <img v-if=\"page.src\" :src=\"page.src\" alt=\"\" class=\"card-image\" />\r\n <div v-else class=\"card-placeholder\">封面/封底</div>\r\n </div>\r\n <div class=\"card-page-number\">{{ page.originalIndex + 1 }} / {{ totalPages }}</div>\r\n </div>\r\n </transition-group>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"controls\" :class=\"{ 'mobile-controls': isMobile, 'desktop-controls': !isMobile, 'controls-visible': showControls }\" @click.stop>\r\n <button @click=\"flipLeft\" class=\"btn btn-prev\">\r\n ← 上一页\r\n </button>\r\n <span class=\"page-indicator\">\r\n <span v-if=\"totalPages === 0\">加载中...</span>\r\n <span v-else-if=\"currentPage === 1 && totalPages > 1\">封面</span>\r\n <span v-else-if=\"currentPage === totalPages && totalPages > 1\">封底</span>\r\n <span v-else-if=\"!isMobile && totalPages > 2\">第 {{ Math.ceil((currentPage - 1) / 2) }} 组 / 共 {{ Math.ceil((totalPages - 2) / 2) }} 组</span>\r\n <span v-else-if=\"isMobile && totalPages > 2\">第 {{ Math.max(1, currentPage ) }} 页 / 共 {{ Math.max(0, totalPages - 2) }} 页</span>\r\n <span v-else-if=\"totalPages === 1\">第 1 页 / 共 1 页</span>\r\n <span v-else>第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span>\r\n </span>\r\n <button @click=\"flipRight\" class=\"btn btn-next\">\r\n 下一页 →\r\n </button>\r\n </div>\r\n\r\n <!-- 页码跳转 - 放在翻页控件下方 -->\r\n <!-- <div class=\"page-jump-container\" :class=\"{ 'mobile-page-jump': isMobile }\" @click.stop @touchstart.stop @touchmove.stop @touchend.stop @mousedown.stop @mouseup.stop>\r\n <input\r\n type=\"text\"\r\n :value=\"jumpPageInput\"\r\n @input=\"handlePageInput\"\r\n :placeholder=\"`搜索跳转`\"\r\n class=\"page-jump-input\"\r\n @click.stop\r\n @mousedown.stop\r\n @mouseup.stop\r\n />\r\n <button @click.stop=\"handleJumpToPage\" @mousedown.stop @mouseup.stop class=\"btn btn-jump\">\r\n 跳转\r\n </button>\r\n </div> -->\r\n\r\n <!-- 翻页模式切换按钮(当URL没有指定模式时显示) -->\r\n <div v-if=\"showFlipModeSelector\" class=\"flip-mode-selector\" :class=\"{ 'mobile-flip-mode-selector': isMobile }\">\r\n <button @click=\"toggleFlipModeMenu\" class=\"btn btn-flip-mode\">\r\n <span class=\"flip-mode-icon\">{{ flipModeIcon }}</span>\r\n <span class=\"flip-mode-text\">{{ flipModeLabel }}</span>\r\n </button>\r\n <div v-if=\"showFlipModeMenu\" class=\"flip-mode-menu\">\r\n <div\r\n v-for=\"mode in flipModes\"\r\n :key=\"mode.value\"\r\n class=\"flip-mode-item\"\r\n :class=\"{ 'flip-mode-item-active': flipMode === mode.value }\"\r\n @click=\"selectFlipMode(mode.value)\"\r\n >\r\n <span class=\"flip-mode-item-icon\">{{ mode.icon }}</span>\r\n <span class=\"flip-mode-item-label\">{{ mode.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 目录按钮 -->\r\n <div\r\n class=\"catalogue-button\"\r\n :class=\"{\r\n 'mobile-catalogue-button': isMobile,\r\n 'dragging': catalogueButtonDragging,\r\n 'catalogue-visible': showControls\r\n }\"\r\n :style=\"{\r\n left: catalogueButtonPosition.x + 'px',\r\n top: catalogueButtonPosition.y + 'px'\r\n }\"\r\n @mousedown=\"onCatalogueMouseDown\"\r\n @touchstart=\"onCatalogueTouchStart\"\r\n @touchmove=\"onCatalogueTouchMove\"\r\n @touchend=\"onCatalogueTouchEnd\"\r\n @click.stop\r\n >\r\n <button @click=\"openCatalogue\" class=\"btn btn-catalogue\">\r\n <!-- <span class=\"drag-handle\">⋮⋮</span> -->\r\n <!-- <span class=\"catalogue-icon\">☰</span> -->\r\n <span class=\"catalogue-text\">目录</span>\r\n </button>\r\n </div>\r\n \r\n <!-- 缩放提示 -->\r\n <div class=\"zoom-hint\" :class=\"{ 'show': zoomScale !== 1 }\">\r\n <div class=\"zoom-info\">\r\n <span>缩放: {{ Math.round(zoomScale * 100) }}%</span>\r\n <button @click=\"resetZoom\" class=\"btn-reset-zoom\">重置</button>\r\n </div>\r\n </div>\r\n\r\n <!-- PC端缩放工具栏 -->\r\n <div v-if=\"!isMobile\" class=\"zoom-toolbar\">\r\n <button @click=\"zoomOut\" class=\"btn-zoom\" :disabled=\"zoomScale <= 0.5\">-</button>\r\n <button @click=\"zoomIn\" class=\"btn-zoom\" :disabled=\"zoomScale >= 3\">+</button>\r\n </div>\r\n \r\n <!-- 加载状态覆盖层 -->\r\n <div v-if=\"loading\" class=\"loading-overlay\">\r\n <div class=\"loading-container\">\r\n <div class=\"loading-spinner\">\r\n <div class=\"spinner-ring\"></div>\r\n <div class=\"spinner-ring\"></div>\r\n <div class=\"spinner-ring\"></div>\r\n </div>\r\n <div class=\"loading-text\">\r\n <h3>正在加载书籍</h3>\r\n <p>请稍候,正在获取精彩内容...</p>\r\n <div class=\"loading-progress\">\r\n <div class=\"progress-bar\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 错误状态 -->\r\n <div v-if=\"error && !loading\" class=\"error-container\">\r\n <div class=\"error-content\">\r\n <div class=\"error-icon\">⚠️</div>\r\n <h3>加载失败</h3>\r\n <p>{{ error.message || '网络连接异常,请检查网络后重试' }}</p>\r\n <button @click=\"fetchBooksData\" class=\"retry-btn\">\r\n <span class=\"retry-icon\">🔄</span>\r\n 重新加载\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- 成功状态提示(短暂显示) -->\r\n <div v-if=\"booksData && !loading && !error\" class=\"success-toast\" :class=\"{ 'show': showSuccessToast }\">\r\n 书籍加载完成\r\n </div>\r\n\r\n <!-- 书籍目录抽屉 -->\r\n <book-catalogue-drawer\r\n v-model=\"showCatalogueDrawer\"\r\n :book-id=\"bookId\"\r\n :catalogue=\"catalogue\"\r\n :loading=\"catalogueLoading\"\r\n @catalogue-click=\"onCatalogueClick\"\r\n @page-jump=\"onPageJump\"\r\n @Focus=\"onFocus\"\r\n @fetch-catalogue=\"onFetchCatalogue\"\r\n />\r\n\r\n <!-- 调试信息 -->\r\n <!-- <div class=\"debug-info\" style=\"margin-top: 20px; color: #6c757d; text-align: center; font-size: 0.9rem;\">\r\n 当前页: {{ currentPage }} / 总页数: {{ totalPages }} | 设备: {{ isMobile ? '移动端' : 'PC端' }}\r\n <br>\r\n <div v-if=\"booksData\" style=\"margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 5px; font-family: monospace; font-size: 0.8rem; text-align: left; max-height: 200px; overflow-y: auto;\">\r\n <strong>接口返回数据:</strong><br>\r\n {{ JSON.stringify(booksData, null, 2) }}\r\n </div>\r\n </div> -->\r\n <!-- <div class=\"debug-info\" style=\"margin-top: 20px; color: #6c757d; text-align: center; font-size: 0.9rem;\">\r\n 当前页: {{ currentPage }} / 总页数: {{ totalPages }} | 设备: {{ isMobile ? '移动端' : 'PC端' }} | 模式: {{ isMobile ? '单页' : '双页' }}\r\n <br>\r\n </div> -->\r\n\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport Flipbook from 'flipbook-vue/vue2'\r\nimport BookCatalogueDrawer from './BookCatalogueDrawer.vue'\r\n\r\nexport default {\r\n name: 'PhotoAlbumView',\r\n components: {\r\n Flipbook,\r\n BookCatalogueDrawer\r\n },\r\n props: {\r\n pages: {\r\n type: Array,\r\n default: () => []\r\n },\r\n bookId: {\r\n type: String,\r\n default: ''\r\n },\r\n startPage: {\r\n type: Number,\r\n default: 1\r\n },\r\n catalogue: {\r\n type: Array,\r\n default: () => []\r\n },\r\n catalogueLoading: {\r\n type: Boolean,\r\n default: false\r\n }\r\n },\r\n data() {\r\n return {\r\n currentPage: 1,\r\n flag: true,\r\n // 设备检测\r\n isMobile: false,\r\n // 接口相关数据\r\n booksData: null,\r\n loading: false,\r\n error: null,\r\n showSuccessToast: false,\r\n // 内容就绪状态\r\n contentReady: false,\r\n // 手势缩放相关\r\n zoomScale: 1,\r\n translateX: 0,\r\n translateY: 0,\r\n lastTouchDistance: 0,\r\n lastTouchCenter: { x: 0, y: 0 },\r\n isZooming: false,\r\n isPanning: false,\r\n touchStartTime: 0,\r\n initialTouches: [],\r\n // Flipbook区域双击检测\r\n flipbookTouchStartTime: 0,\r\n flipbookLastTapTime: 0,\r\n // 目录相关\r\n showCatalogueDrawer: false,\r\n // 目录按钮拖拽相关\r\n catalogueButtonDragging: false,\r\n catalogueButtonPosition: { x: 20, y: 20 },\r\n dragStartPosition: { x: 0, y: 0 },\r\n dragOffset: { x: 0, y: 0 },\r\n // 翻页模式相关\r\n flipMode: 'flip', // flip: 仿真翻页, slide: 滑动翻页, fade: 淡入淡出, scroll: 垂直滚动\r\n showFlipModeMenu: false,\r\n flipModes: [\r\n { value: 'flip', label: '仿真翻页', icon: '📖' },\r\n { value: 'slide', label: '滑动翻页', icon: '↔️' },\r\n { value: 'fade', label: '淡入淡出', icon: '✨' },\r\n { value: 'scroll', label: '垂直滚动', icon: '📜' },\r\n { value: 'clip', label: '截断特效', icon: '✂️' },\r\n { value: 'card', label: '卡片风格', icon: '🃏' }\r\n ],\r\n // 滑动模式相关\r\n slideStartX: 0,\r\n slideCurrentX: 0,\r\n slideIsDragging: false,\r\n // URL参数控制\r\n flipModeFromUrl: false, // 翻页模式是否由URL参数指定\r\n // 控件自动隐藏相关\r\n showControls: false,\r\n hideControlsTimer: null,\r\n // 页码跳转相关\r\n jumpPageInput: ''\r\n }\r\n },\r\n computed: {\r\n totalPages() {\r\n return this.pages.length\r\n },\r\n pagesHiRes() {\r\n return this.pages\r\n },\r\n /**\r\n * 翻页模式图标\r\n */\r\n flipModeIcon() {\r\n const mode = this.flipModes.find(m => m.value === this.flipMode)\r\n return mode ? mode.icon : '📖'\r\n },\r\n /**\r\n * 翻页模式标签\r\n */\r\n flipModeLabel() {\r\n const mode = this.flipModes.find(m => m.value === this.flipMode)\r\n return mode ? mode.label : '仿真翻页'\r\n },\r\n /**\r\n * 滑动容器样式\r\n */\r\n slideContainerStyle() {\r\n const offset = -(this.currentPage - 1) * 100\r\n const dragOffset = this.slideIsDragging ? (this.slideCurrentX - this.slideStartX) / 5 : 0\r\n return {\r\n transform: `translateX(calc(${offset}% + ${dragOffset}px))`,\r\n transition: this.slideIsDragging ? 'none' : 'transform 0.3s ease-out'\r\n }\r\n },\r\n /**\r\n * 是否显示翻页模式选择器\r\n */\r\n showFlipModeSelector() {\r\n // 如果翻页模式是由URL参数指定的,则隐藏选择器\r\n return !this.flipModeFromUrl\r\n },\r\n /**\r\n * 卡片模式可见卡片\r\n */\r\n visibleCards() {\r\n const cards = []\r\n // 上一页\r\n if (this.currentPage > 1) {\r\n cards.push({\r\n src: this.pages[this.currentPage - 2],\r\n originalIndex: this.currentPage - 2,\r\n position: 'prev'\r\n })\r\n }\r\n // 当前页\r\n cards.push({\r\n src: this.pages[this.currentPage - 1],\r\n originalIndex: this.currentPage - 1,\r\n position: 'current'\r\n })\r\n // 下一页\r\n if (this.currentPage < this.totalPages) {\r\n cards.push({\r\n src: this.pages[this.currentPage],\r\n originalIndex: this.currentPage,\r\n position: 'next'\r\n })\r\n }\r\n return cards\r\n },\r\n /**\r\n * 计算当前页面显示文本\r\n */\r\n pageDisplayText() {\r\n if (this.totalPages === 0) return '加载中...'\r\n if (this.currentPage === 1 && this.totalPages > 1) return '封面'\r\n if (this.currentPage === this.totalPages && this.totalPages > 1) return '封底'\r\n \r\n if (this.isMobile) {\r\n if (this.totalPages <= 2) return `第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`\r\n return `第 ${Math.max(1, this.currentPage - 1)} 页 / 共 ${Math.max(0, this.totalPages - 2)} 页`\r\n } else {\r\n if (this.totalPages <= 2) return `第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`\r\n return `第 ${Math.ceil((this.currentPage - 1) / 2)} 组 / 共 ${Math.ceil((this.totalPages - 2) / 2)} 组`\r\n }\r\n }\r\n },\r\n methods: {\r\n /**\r\n * 获取书籍数据\r\n */\r\n async fetchBooksData() {\r\n try {\r\n this.loading = true\r\n this.error = null\r\n\r\n // 注释:原来从URL参数获取fileManagementId,现在改为通过props传入\r\n // const fileManagementId = this.$route.params.id || this.$route.query.id || ''\r\n // this.bookId = fileManagementId\r\n\r\n // 注释:原来调用API获取数据,现在改为通过props传入\r\n // const response = await booksApi.getBooksSlice(fileManagementId, textContent, 1, 9999)\r\n\r\n // 触发事件让父组件处理数据获取\r\n this.$emit('fetch-books-data', this.bookId)\r\n\r\n // 如果通过props传入了pages数据,直接使用\r\n if (this.pages && this.pages.length > 0) {\r\n this.booksData = { data: { records: this.pages.map(url => ({ imageUrl: url })) } }\r\n\r\n // 显示成功提示\r\n this.showSuccessToast = true\r\n setTimeout(() => {\r\n this.showSuccessToast = false\r\n }, 3000)\r\n\r\n // 检查URL参数并跳转到指定页码\r\n this.checkUrlPageParameter()\r\n }\r\n \r\n } catch (error) {\r\n console.error('获取书籍数据失败:', error)\r\n this.error = error\r\n \r\n // 显示错误信息\r\n if (error.response) {\r\n console.error('API错误响应:', error.response.data)\r\n } else if (error.request) {\r\n console.error('网络请求失败:', error.request)\r\n } else {\r\n console.error('请求配置错误:', error.message)\r\n }\r\n } finally {\r\n this.loading = false\r\n }\r\n },\r\n\r\n /**\r\n * 根据API数据更新页面\r\n * @param {Array} apiData - API返回的数据\r\n */\r\n updatePagesFromApiData(apiData) {\r\n if (!Array.isArray(apiData)) {\r\n console.warn('API数据格式不正确,期望数组格式')\r\n return\r\n }\r\n \r\n console.log('开始更新页面数据,数据长度:', apiData.length)\r\n \r\n // 这里可以根据实际的API数据结构来处理\r\n // 假设API返回的数据包含图片URL\r\n const newPages = [null] // 保留封面页\r\n \r\n apiData.forEach((item, index) => {\r\n if (item.imageUrl) {\r\n newPages.push(item.imageUrl)\r\n console.log(`添加页面 ${index + 1}:`, item.imageUrl)\r\n }\r\n })\r\n \r\n if (newPages.length > 1) {\r\n this.pages = newPages\r\n console.log('页面数据更新完成,总页数:', this.pages.length)\r\n }\r\n },\r\n\r\n /**\r\n * 检测是否为移动设备\r\n */\r\n detectMobile() {\r\n const userAgent = navigator.userAgent.toLowerCase()\r\n const mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone']\r\n\r\n // 检测 User Agent\r\n const isMobileUA = mobileKeywords.some(keyword => userAgent.includes(keyword))\r\n\r\n // 检测屏幕尺寸\r\n const isMobileScreen = window.innerWidth <= 768\r\n\r\n // 检测触摸支持\r\n const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0\r\n\r\n // 综合判断\r\n this.isMobile = isMobileUA || (isMobileScreen && isTouchDevice)\r\n\r\n console.log('设备检测结果:', {\r\n userAgent: isMobileUA,\r\n screenSize: isMobileScreen,\r\n touchSupport: isTouchDevice,\r\n finalResult: this.isMobile\r\n })\r\n },\r\n\r\n /**\r\n * 预加载首页图片\r\n */\r\n async preloadFirstPage() {\r\n if (!this.pages || this.pages.length === 0) {\r\n this.contentReady = true\r\n return\r\n }\r\n\r\n const firstPageIndex = this.startPage - 1\r\n const firstPageUrl = this.pages[firstPageIndex]\r\n\r\n if (!firstPageUrl) {\r\n this.contentReady = true\r\n return\r\n }\r\n\r\n try {\r\n await new Promise((resolve, reject) => {\r\n const img = new Image()\r\n img.onload = () => resolve()\r\n img.onerror = () => resolve() // 即使失败也继续\r\n img.src = firstPageUrl\r\n\r\n // 超时保护\r\n setTimeout(() => resolve(), 3000)\r\n })\r\n } catch (error) {\r\n console.warn('首页图片预加载失败:', error)\r\n } finally {\r\n this.contentReady = true\r\n }\r\n },\r\n \r\n /**\r\n * 监听窗口尺寸变化\r\n */\r\n handleResize() {\r\n const wasMobile = this.isMobile\r\n this.detectMobile()\r\n \r\n // 如果设备类型发生变化,重新初始化flipbook\r\n if (wasMobile !== this.isMobile) {\r\n console.log('设备类型变化,重新初始化')\r\n this.$nextTick(() => {\r\n if (this.$refs.flipbook) {\r\n // 重置到第一页\r\n this.currentPage = 1\r\n this.startPage = 1\r\n }\r\n })\r\n }\r\n },\r\n flipLeft() {\r\n console.log('尝试向左翻页,当前页:', this.currentPage)\r\n if (this.flipMode === 'flip') {\r\n // 仿真翻页模式\r\n if (this.$refs.flipbook && this.currentPage > 1) {\r\n try {\r\n this.$refs.flipbook.flipLeft()\r\n console.log('向左翻页命令已发送')\r\n } catch (error) {\r\n console.error('向左翻页失败:', error)\r\n }\r\n } else {\r\n console.log('无法向左翻页 - 已在第一页或组件未就绪')\r\n }\r\n } else {\r\n // 其他模式直接切换页码\r\n if (this.currentPage > 1) {\r\n this.currentPage--\r\n this.onFlipLeftEnd(this.currentPage)\r\n }\r\n }\r\n },\r\n flipRight() {\r\n console.log('尝试向右翻页,当前页:', this.currentPage)\r\n if (this.flipMode === 'flip') {\r\n // 仿真翻页模式\r\n if (this.$refs.flipbook && this.currentPage < this.totalPages) {\r\n try {\r\n this.$refs.flipbook.flipRight()\r\n console.log('向右翻页命令已发送')\r\n } catch (error) {\r\n console.error('向右翻页失败:', error)\r\n }\r\n } else {\r\n console.log('无法向右翻页 - 已在最后一页或组件未就绪')\r\n }\r\n } else {\r\n // 其他模式直接切换页码\r\n if (this.currentPage < this.totalPages) {\r\n this.currentPage++\r\n this.onFlipRightEnd(this.currentPage)\r\n }\r\n }\r\n },\r\n onFlipLeftEnd(page) {\r\n console.log('向左翻页完成,新页面:', page)\r\n this.currentPage = page\r\n // 触发页码变化事件\r\n this.$emit('page-change', page)\r\n // 记录阅读进度\r\n this.recordReadingProgress(page)\r\n },\r\n onFlipRightEnd(page) {\r\n console.log('向右翻页完成,新页面:', page)\r\n this.currentPage = page\r\n // 触发页码变化事件\r\n this.$emit('page-change', page)\r\n // 记录阅读进度\r\n this.recordReadingProgress(page)\r\n },\r\n \r\n /**\r\n * 容器触摸开始事件\r\n */\r\n onContainerTouchStart(e) {\r\n this.touchStartTime = Date.now()\r\n this.initialTouches = Array.from(e.touches)\r\n \r\n if (e.touches.length === 2) {\r\n // 双指缩放\r\n this.isZooming = true\r\n this.lastTouchDistance = this.getTouchDistance(e.touches[0], e.touches[1])\r\n this.lastTouchCenter = this.getTouchCenter(e.touches[0], e.touches[1])\r\n e.preventDefault()\r\n } else if (e.touches.length === 1 && this.zoomScale > 1) {\r\n // 单指拖拽(仅在放大状态下)\r\n this.isPanning = true\r\n this.lastTouchCenter = { x: e.touches[0].clientX, y: e.touches[0].clientY }\r\n }\r\n },\r\n \r\n /**\r\n * 容器触摸移动事件\r\n */\r\n onContainerTouchMove(e) {\r\n if (this.isZooming && e.touches.length === 2) {\r\n // 双指缩放\r\n const currentDistance = this.getTouchDistance(e.touches[0], e.touches[1])\r\n const currentCenter = this.getTouchCenter(e.touches[0], e.touches[1])\r\n \r\n // 计算缩放比例\r\n const scaleChange = currentDistance / this.lastTouchDistance\r\n let newScale = this.zoomScale * scaleChange\r\n \r\n // 限制缩放范围\r\n newScale = Math.max(0.5, Math.min(3, newScale))\r\n \r\n // 计算缩放中心点的偏移\r\n const deltaX = currentCenter.x - this.lastTouchCenter.x\r\n const deltaY = currentCenter.y - this.lastTouchCenter.y\r\n \r\n this.zoomScale = newScale\r\n this.translateX += deltaX\r\n this.translateY += deltaY\r\n \r\n this.lastTouchDistance = currentDistance\r\n this.lastTouchCenter = currentCenter\r\n \r\n e.preventDefault()\r\n } else if (this.isPanning && e.touches.length === 1 && this.zoomScale > 1) {\r\n // 单指拖拽\r\n const deltaX = e.touches[0].clientX - this.lastTouchCenter.x\r\n const deltaY = e.touches[0].clientY - this.lastTouchCenter.y\r\n \r\n this.translateX += deltaX\r\n this.translateY += deltaY\r\n \r\n this.lastTouchCenter = { x: e.touches[0].clientX, y: e.touches[0].clientY }\r\n \r\n e.preventDefault()\r\n }\r\n },\r\n \r\n /**\r\n * 容器触摸结束事件\r\n */\r\n onContainerTouchEnd(e) {\r\n const touchDuration = Date.now() - this.touchStartTime\r\n const wasZooming = this.isZooming\r\n const wasPanning = this.isPanning\r\n\r\n // 先重置状态\r\n this.isZooming = false\r\n this.isPanning = false\r\n\r\n // 双击重置缩放 - 只在单指快速点击时检测\r\n if (e.changedTouches.length === 1 && touchDuration < 300 && !wasZooming && !wasPanning) {\r\n const now = Date.now()\r\n if (this.lastTapTime && now - this.lastTapTime < 400) {\r\n // 检测到双击\r\n this.resetZoom()\r\n console.log('移动端双击重置显示比例')\r\n this.lastTapTime = 0 // 重置,避免三击触发\r\n } else {\r\n this.lastTapTime = now\r\n }\r\n }\r\n\r\n // 边界检查和回弹\r\n this.constrainPosition()\r\n },\r\n \r\n /**\r\n * 获取两点间距离\r\n */\r\n getTouchDistance(touch1, touch2) {\r\n const dx = touch1.clientX - touch2.clientX\r\n const dy = touch1.clientY - touch2.clientY\r\n return Math.sqrt(dx * dx + dy * dy)\r\n },\r\n \r\n /**\r\n * 获取两点中心\r\n */\r\n getTouchCenter(touch1, touch2) {\r\n return {\r\n x: (touch1.clientX + touch2.clientX) / 2,\r\n y: (touch1.clientY + touch2.clientY) / 2\r\n }\r\n },\r\n \r\n /**\r\n * 重置缩放\r\n */\r\n resetZoom() {\r\n this.zoomScale = 1\r\n this.translateX = 0\r\n this.translateY = 0\r\n },\r\n\r\n /**\r\n * 放大\r\n */\r\n zoomIn() {\r\n this.zoomScale = Math.min(3, this.zoomScale + 0.2)\r\n },\r\n\r\n /**\r\n * 缩小\r\n */\r\n zoomOut() {\r\n this.zoomScale = Math.max(0.5, this.zoomScale - 0.2)\r\n },\r\n \r\n /**\r\n * PC端双击事件处理\r\n */\r\n onContainerDoubleClick(e) {\r\n // 阻止事件冒泡和默认行为,避免触发翻页\r\n e.preventDefault()\r\n e.stopPropagation()\r\n\r\n // 重置缩放比例\r\n this.resetZoom()\r\n console.log('容器双击重置显示比例,当前缩放:', this.zoomScale)\r\n },\r\n\r\n /**\r\n * Flipbook区域双击事件处理\r\n */\r\n onFlipbookDoubleClick(e) {\r\n // 阻止事件冒泡和默认行为,避免触发翻页\r\n e.preventDefault()\r\n e.stopPropagation()\r\n\r\n // 重置缩放比例\r\n this.resetZoom()\r\n console.log('Flipbook双击重置显示比例,当前缩放:', this.zoomScale)\r\n },\r\n\r\n /**\r\n * Flipbook区域触摸开始事件\r\n */\r\n onFlipbookTouchStart(e) {\r\n this.flipbookTouchStartTime = Date.now()\r\n },\r\n\r\n /**\r\n * Flipbook区域触摸结束事件(移动端双击检测)\r\n */\r\n onFlipbookTouchEnd(e) {\r\n const touchDuration = Date.now() - this.flipbookTouchStartTime\r\n\r\n // 双击重置缩放 - 只在单指快速点击时检测\r\n if (e.changedTouches.length === 1 && touchDuration < 300) {\r\n const now = Date.now()\r\n if (this.flipbookLastTapTime && now - this.flipbookLastTapTime < 400) {\r\n // 检测到双击\r\n e.preventDefault()\r\n e.stopPropagation()\r\n this.resetZoom()\r\n console.log('Flipbook区域移动端双击重置显示比例')\r\n this.flipbookLastTapTime = 0 // 重置,避免三击触发\r\n } else {\r\n this.flipbookLastTapTime = now\r\n }\r\n }\r\n },\r\n \r\n /**\r\n * 约束位置在边界内\r\n */\r\n constrainPosition() {\r\n if (this.zoomScale <= 1) {\r\n this.translateX = 0\r\n this.translateY = 0\r\n return\r\n }\r\n \r\n const maxTranslate = (this.zoomScale - 1) * 200\r\n this.translateX = Math.max(-maxTranslate, Math.min(maxTranslate, this.translateX))\r\n this.translateY = Math.max(-maxTranslate, Math.min(maxTranslate, this.translateY))\r\n },\r\n\r\n\r\n /**\r\n * 打开目录抽屉\r\n */\r\n openCatalogue() {\r\n console.log('点击目录按钮', {\r\n dragging: this.catalogueButtonDragging,\r\n showDrawer: this.showCatalogueDrawer\r\n })\r\n \r\n // 只有在不是拖拽状态下才打开目录\r\n if (!this.catalogueButtonDragging) {\r\n this.showCatalogueDrawer = true\r\n console.log('目录抽屉已打开')\r\n } else {\r\n console.log('拖拽状态中,不打开目录')\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮拖拽开始\r\n */\r\n onCatalogueMouseDown(e) {\r\n // 不立即设置为拖拽状态,等待移动再判断\r\n this.catalogueButtonDragging = false\r\n \r\n const rect = e.target.closest('.catalogue-button').getBoundingClientRect()\r\n this.dragStartPosition = {\r\n x: e.clientX,\r\n y: e.clientY\r\n }\r\n this.dragOffset = {\r\n x: e.clientX - rect.left,\r\n y: e.clientY - rect.top\r\n }\r\n \r\n document.addEventListener('mousemove', this.onCatalogueMouseMove)\r\n document.addEventListener('mouseup', this.onCatalogueMouseUp)\r\n },\r\n\r\n /**\r\n * 目录按钮拖拽移动\r\n */\r\n onCatalogueMouseMove(e) {\r\n // 检查是否开始拖拽\r\n const dragDistance = Math.sqrt(\r\n Math.pow(e.clientX - this.dragStartPosition.x, 2) + \r\n Math.pow(e.clientY - this.dragStartPosition.y, 2)\r\n )\r\n \r\n // 只有移动距离超过5px才认为是拖拽\r\n if (dragDistance > 5) {\r\n this.catalogueButtonDragging = true\r\n }\r\n \r\n if (!this.catalogueButtonDragging) return\r\n \r\n e.preventDefault()\r\n \r\n const newX = e.clientX - this.dragOffset.x\r\n const newY = e.clientY - this.dragOffset.y\r\n \r\n // 限制在视窗范围内\r\n const maxX = window.innerWidth - 120 // 按钮宽度约120px\r\n const maxY = window.innerHeight - 50 // 按钮高度约50px\r\n \r\n this.catalogueButtonPosition = {\r\n x: Math.max(0, Math.min(maxX, newX)),\r\n y: Math.max(0, Math.min(maxY, newY))\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮拖拽结束\r\n */\r\n onCatalogueMouseUp(e) {\r\n // 立即重置拖拽状态\r\n this.catalogueButtonDragging = false\r\n \r\n document.removeEventListener('mousemove', this.onCatalogueMouseMove)\r\n document.removeEventListener('mouseup', this.onCatalogueMouseUp)\r\n },\r\n\r\n /**\r\n * 目录按钮触摸开始(移动端)\r\n */\r\n onCatalogueTouchStart(e) {\r\n // 不立即设置为拖拽状态\r\n this.catalogueButtonDragging = false\r\n \r\n const touch = e.touches[0]\r\n const rect = e.target.closest('.catalogue-button').getBoundingClientRect()\r\n \r\n this.dragStartPosition = {\r\n x: touch.clientX,\r\n y: touch.clientY\r\n }\r\n this.dragOffset = {\r\n x: touch.clientX - rect.left,\r\n y: touch.clientY - rect.top\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮触摸移动(移动端)\r\n */\r\n onCatalogueTouchMove(e) {\r\n const touch = e.touches[0]\r\n \r\n // 检查是否开始拖拽\r\n const dragDistance = Math.sqrt(\r\n Math.pow(touch.clientX - this.dragStartPosition.x, 2) + \r\n Math.pow(touch.clientY - this.dragStartPosition.y, 2)\r\n )\r\n \r\n // 只有移动距离超过5px才认为是拖拽\r\n if (dragDistance > 5) {\r\n this.catalogueButtonDragging = true\r\n }\r\n \r\n if (!this.catalogueButtonDragging) return\r\n \r\n e.preventDefault()\r\n \r\n const newX = touch.clientX - this.dragOffset.x\r\n const newY = touch.clientY - this.dragOffset.y\r\n \r\n // 限制在视窗范围内,考虑安全区域\r\n const maxX = window.innerWidth - 120\r\n const maxY = window.innerHeight - 80\r\n \r\n this.catalogueButtonPosition = {\r\n x: Math.max(15, Math.min(maxX, newX)),\r\n y: Math.max(15, Math.min(maxY, newY))\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮触摸结束(移动端)\r\n */\r\n onCatalogueTouchEnd(e) {\r\n // 立即重置拖拽状态\r\n this.catalogueButtonDragging = false\r\n },\r\n \r\n /**\r\n * 切换翻页模式菜单显示\r\n */\r\n toggleFlipModeMenu() {\r\n this.showFlipModeMenu = !this.showFlipModeMenu\r\n },\r\n \r\n /**\r\n * 选择翻页模式\r\n * @param {string} mode - 翻页模式\r\n */\r\n selectFlipMode(mode) {\r\n const oldMode = this.flipMode\r\n this.flipMode = mode\r\n this.showFlipModeMenu = false\r\n \r\n // 保存翻页模式到本地存储\r\n localStorage.setItem('book-viewer-flip-mode', mode)\r\n \r\n console.log('切换翻页模式:', { from: oldMode, to: mode })\r\n \r\n // 如果切换到滚动模式,需要滚动到当前页\r\n if (mode === 'scroll') {\r\n this.$nextTick(() => {\r\n this.scrollToCurrentPage()\r\n })\r\n }\r\n },\r\n \r\n /**\r\n * 滑动模式触摸开始\r\n */\r\n onSlideViewerTouchStart(e) {\r\n if (e.touches.length === 1) {\r\n this.slideStartX = e.touches[0].clientX\r\n this.slideCurrentX = e.touches[0].clientX\r\n this.slideIsDragging = true\r\n }\r\n },\r\n \r\n /**\r\n * 滑动模式触摸移动\r\n */\r\n onSlideViewerTouchMove(e) {\r\n if (this.slideIsDragging && e.touches.length === 1) {\r\n this.slideCurrentX = e.touches[0].clientX\r\n e.preventDefault()\r\n }\r\n },\r\n \r\n /**\r\n * 滑动模式触摸结束\r\n */\r\n onSlideViewerTouchEnd(e) {\r\n if (this.slideIsDragging) {\r\n const deltaX = this.slideCurrentX - this.slideStartX\r\n const threshold = 50 // 滑动阈值\r\n \r\n if (deltaX > threshold && this.currentPage > 1) {\r\n // 向右滑动,上一页\r\n this.currentPage--\r\n this.recordReadingProgress(this.currentPage)\r\n } else if (deltaX < -threshold && this.currentPage < this.totalPages) {\r\n // 向左滑动,下一页\r\n this.currentPage++\r\n this.recordReadingProgress(this.currentPage)\r\n }\r\n \r\n this.slideIsDragging = false\r\n }\r\n },\r\n \r\n /**\r\n * 滚动模式滚动事件\r\n */\r\n onScrollViewerScroll(e) {\r\n const container = this.$refs.scrollViewer\r\n if (!container) return\r\n \r\n const scrollTop = container.scrollTop\r\n const containerHeight = container.clientHeight\r\n \r\n // 计算当前页码\r\n for (let i = 0; i < this.pages.length; i++) {\r\n const pageRef = this.$refs['scrollPage' + i]\r\n if (pageRef && pageRef[0]) {\r\n const pageTop = pageRef[0].offsetTop\r\n const pageBottom = pageTop + pageRef[0].offsetHeight\r\n \r\n if (scrollTop >= pageTop - containerHeight / 2 && scrollTop < pageBottom - containerHeight / 2) {\r\n if (this.currentPage !== i + 1) {\r\n this.currentPage = i + 1\r\n this.recordReadingProgress(this.currentPage)\r\n }\r\n break\r\n }\r\n }\r\n }\r\n },\r\n \r\n /**\r\n * 滚动到当前页\r\n */\r\n scrollToCurrentPage() {\r\n if (this.flipMode !== 'scroll') return\r\n \r\n const pageRef = this.$refs['scrollPage' + (this.currentPage - 1)]\r\n if (pageRef && pageRef[0]) {\r\n pageRef[0].scrollIntoView({ behavior: 'smooth', block: 'start' })\r\n }\r\n },\r\n \r\n /**\r\n * 关闭翻页模式菜单(点击外部)\r\n */\r\n closeFlipModeMenu(e) {\r\n if (!e.target.closest('.flip-mode-selector')) {\r\n this.showFlipModeMenu = false\r\n }\r\n },\r\n \r\n /**\r\n * 获取卡片样式\r\n * @param {string} position - 卡片位置:prev, current, next\r\n */\r\n getCardStyle(position) {\r\n switch (position) {\r\n case 'prev':\r\n return {\r\n transform: 'translateX(-70%) scale(0.85) rotateY(15deg)',\r\n zIndex: 1,\r\n opacity: 0.6,\r\n filter: 'brightness(0.8)'\r\n }\r\n case 'current':\r\n return {\r\n transform: 'translateX(0) scale(1) rotateY(0deg)',\r\n zIndex: 3,\r\n opacity: 1,\r\n filter: 'brightness(1)'\r\n }\r\n case 'next':\r\n return {\r\n transform: 'translateX(70%) scale(0.85) rotateY(-15deg)',\r\n zIndex: 1,\r\n opacity: 0.6,\r\n filter: 'brightness(0.8)'\r\n }\r\n default:\r\n return {}\r\n }\r\n },\r\n\r\n /**\r\n * 目录项点击事件\r\n * @param {Object} item - 点击的目录项\r\n */\r\n onCatalogueClick(item) {\r\n console.log('目录项被点击:', item)\r\n this.$emit('catalogue-click', item)\r\n },\r\n\r\n /**\r\n * 获取目录数据事件\r\n * @param {String} bookId - 书籍ID\r\n */\r\n onFetchCatalogue(bookId) {\r\n this.$emit('fetch-catalogue', bookId)\r\n },\r\n\r\n /**\r\n * 页面跳转事件\r\n * @param {number} pageNumber - 目标页码\r\n */\r\n onFocus() {\r\n // 使用flipbook的方法跳转到指定页面\r\n if (this.flipMode === 'flip') {\r\n this.flag = false\r\n setTimeout(() => {\r\n this.flag = true\r\n }, 500)\r\n } else if (this.flipMode === 'scroll') {\r\n this.scrollToCurrentPage()\r\n }\r\n },\r\n onPageJump(pageNumber) {\r\n console.log('跳转到页面:', pageNumber)\r\n\r\n // 关闭目录抽屉\r\n this.showCatalogueDrawer = false\r\n\r\n // 跳转到指定页面\r\n if (pageNumber && pageNumber >= 1 && pageNumber <= this.totalPages) {\r\n this.currentPage = pageNumber\r\n this.startPage = pageNumber\r\n\r\n // 记录阅读进度\r\n this.recordReadingProgress(pageNumber)\r\n\r\n // 使用flipbook的方法跳转到指定页面\r\n if (this.flipMode === 'flip') {\r\n this.flag = false\r\n setTimeout(() => {\r\n this.flag = true\r\n this.$nextTick(() => {\r\n this.$refs.flipbook.goToPage(pageNumber)\r\n })\r\n }, 500)\r\n } else if (this.flipMode === 'scroll') {\r\n this.scrollToCurrentPage()\r\n }\r\n } else {\r\n console.warn('无效的页码:', pageNumber)\r\n }\r\n },\r\n\r\n /**\r\n * 检查URL参数并跳转到指定页码\r\n */\r\n checkUrlPageParameter() {\r\n // 从URL查询参数中获取页码\r\n const pageParam = this.$route.query.page\r\n \r\n if (pageParam) {\r\n const targetPage = parseInt(pageParam, 10)\r\n \r\n console.log('检测到URL页码参数:', {\r\n pageParam,\r\n targetPage,\r\n totalPages: this.totalPages\r\n })\r\n \r\n // 验证页码是否有效\r\n if (!isNaN(targetPage) && targetPage >= 1 && targetPage <= this.totalPages) {\r\n console.log('准备跳转到页码:', targetPage)\r\n \r\n // 使用nextTick确保DOM已更新\r\n this.$nextTick(() => {\r\n this.jumpToPage(targetPage)\r\n })\r\n } else {\r\n console.warn('无效的页码参数:', {\r\n pageParam,\r\n targetPage,\r\n totalPages: this.totalPages,\r\n isValid: !isNaN(targetPage) && targetPage >= 1 && targetPage <= this.totalPages\r\n })\r\n }\r\n } else {\r\n console.log('URL中未检测到页码参数')\r\n }\r\n },\r\n\r\n /**\r\n * 跳转到指定页码\r\n * @param {number} pageNumber - 目标页码\r\n */\r\n jumpToPage(pageNumber) {\r\n if (!pageNumber || pageNumber < 1 || pageNumber > this.totalPages) {\r\n console.warn('无效的页码:', pageNumber)\r\n return\r\n }\r\n\r\n console.log('跳转到页面:', pageNumber)\r\n \r\n // 更新当前页码和起始页码\r\n this.currentPage = pageNumber\r\n this.startPage = pageNumber\r\n \r\n // 记录阅读进度\r\n this.recordReadingProgress(pageNumber)\r\n \r\n // 使用flipbook的方法跳转到指定页面\r\n if (this.flipMode === 'flip') {\r\n if (this.$refs.flipbook && this.$refs.flipbook.goToPage) {\r\n try {\r\n this.$refs.flipbook.goToPage(pageNumber)\r\n console.log('页面跳转成功:', pageNumber)\r\n } catch (error) {\r\n console.error('页面跳转失败:', error)\r\n }\r\n } else {\r\n console.warn('Flipbook组件未就绪,无法跳转页面')\r\n }\r\n } else if (this.flipMode === 'scroll') {\r\n this.scrollToCurrentPage()\r\n }\r\n // slide 和 fade 模式直接通过 currentPage 变化来切换\r\n },\r\n\r\n /**\r\n * 记录阅读进度\r\n * @param {number} pageNumber - 当前页码\r\n */\r\n async recordReadingProgress(pageNumber) {\r\n if (!this.bookId || !pageNumber) {\r\n console.warn('缺少书籍ID或页码,无法记录阅读进度')\r\n return\r\n }\r\n\r\n try {\r\n console.log('记录阅读进度:', {\r\n bookId: this.bookId,\r\n pageNumber: pageNumber\r\n })\r\n\r\n // 注释:原来调用API记录进度,现在改为触发事件让父组件处理\r\n // await booksApi.recordReadProgress(this.bookId, pageNumber)\r\n this.$emit('record-progress', { bookId: this.bookId, pageNumber })\r\n\r\n console.log('阅读进度记录成功')\r\n\r\n } catch (error) {\r\n console.error('记录阅读进度失败:', error)\r\n // 阅读记录失败不影响用户体验,只记录日志\r\n }\r\n },\r\n\r\n /**\r\n * 显示控件\r\n */\r\n showControlsTemporarily() {\r\n this.showControls = true\r\n\r\n // 清除之前的定时器\r\n if (this.hideControlsTimer) {\r\n clearTimeout(this.hideControlsTimer)\r\n }\r\n\r\n // 3秒后自动隐藏\r\n this.hideControlsTimer = setTimeout(() => {\r\n this.showControls = false\r\n }, 3000)\r\n },\r\n\r\n /**\r\n * 内容区域点击事件\r\n */\r\n onContentClick() {\r\n this.showControlsTemporarily()\r\n },\r\n\r\n /**\r\n * 处理页码输入\r\n */\r\n handlePageInput(e) {\r\n const value = e.target.value\r\n // 只允许输入数字\r\n const numValue = value.replace(/[^\\d]/g, '')\r\n // 限制不超过总页数\r\n if (numValue && parseInt(numValue) > this.totalPages) {\r\n this.jumpPageInput = this.totalPages.toString()\r\n } else {\r\n this.jumpPageInput = numValue\r\n }\r\n },\r\n\r\n /**\r\n * 执行页码跳转\r\n */\r\n handleJumpToPage() {\r\n const pageNum = parseInt(this.jumpPageInput)\r\n if (pageNum && pageNum >= 1 && pageNum <= this.totalPages) {\r\n // 重置缩放和位移,避免内容区域缩小\r\n this.resetZoom()\r\n this.jumpToPage(pageNum)\r\n this.jumpPageInput = ''\r\n }\r\n }\r\n },\r\n watch: {\r\n async pages(newPages) {\r\n if (newPages && newPages.length > 0 && this.isMobile) {\r\n this.contentReady = false\r\n await this.$nextTick()\r\n await this.preloadFirstPage()\r\n } else if (newPages && newPages.length > 0) {\r\n this.contentReady = true\r\n }\r\n }\r\n },\r\n async mounted() {\r\n console.log('组件已挂载,总页数:', this.totalPages)\r\n\r\n // 检测设备类型\r\n this.detectMobile()\r\n\r\n // 检查URL参数中的翻页模式\r\n const urlFlipMode = this.$route.query.mode || this.$route.query.flipMode\r\n if (urlFlipMode && this.flipModes.some(m => m.value === urlFlipMode)) {\r\n // URL参数指定的翻页模式优先级最高,并隐藏切换按钮\r\n this.flipMode = urlFlipMode\r\n this.flipModeFromUrl = true\r\n console.log('从URL参数读取翻页模式:', urlFlipMode)\r\n } else {\r\n // 否则从本地存储恢复\r\n const savedFlipMode = localStorage.getItem('book-viewer-flip-mode')\r\n if (savedFlipMode && this.flipModes.some(m => m.value === savedFlipMode)) {\r\n this.flipMode = savedFlipMode\r\n }\r\n }\r\n\r\n // 预加载首页图片(移动端优化)\r\n if (this.isMobile && this.pages && this.pages.length > 0) {\r\n await this.$nextTick()\r\n await this.preloadFirstPage()\r\n } else if (this.pages && this.pages.length > 0) {\r\n this.contentReady = true\r\n }\r\n\r\n // 监听窗口尺寸变化\r\n window.addEventListener('resize', this.handleResize)\r\n\r\n // 监听点击事件,用于关闭翻页模式菜单\r\n document.addEventListener('click', this.closeFlipModeMenu)\r\n },\r\n \r\n beforeDestroy() {\r\n // 清理事件监听\r\n window.removeEventListener('resize', this.handleResize)\r\n // 清理拖拽事件监听\r\n document.removeEventListener('mousemove', this.onCatalogueMouseMove)\r\n document.removeEventListener('mouseup', this.onCatalogueMouseUp)\r\n // 清理翻页模式菜单事件监听\r\n document.removeEventListener('click', this.closeFlipModeMenu)\r\n // 清理控件自动隐藏定时器\r\n if (this.hideControlsTimer) {\r\n clearTimeout(this.hideControlsTimer)\r\n }\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.photo-album-container {\r\n /* min-height: 100vh; */\r\n background: transparent;\r\n padding: 5px 5px 80px 5px;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n transform-origin: center center;\r\n transition: transform 0.2s ease-out;\r\n overflow: hidden;\r\n position: relative;\r\n}\r\n\r\n.album-header {\r\n text-align: center;\r\n color: #495057;\r\n margin-bottom: 30px;\r\n}\r\n\r\n.album-header h1 {\r\n font-size: 2.5rem;\r\n margin-bottom: 10px;\r\n text-shadow: 1px 1px 2px rgba(0,0,0,0.1);\r\n font-weight: 600;\r\n}\r\n\r\n.album-header p {\r\n font-size: 1.1rem;\r\n opacity: 0.9;\r\n}\r\n\r\n.flipbook-wrapper {\r\n perspective: 2000px;\r\n margin-bottom: 20px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n width: 100%;\r\n}\r\n\r\n.flipbook {\r\n width: 1600px;\r\n height: 1200px;\r\n box-shadow: none;\r\n border-radius: 0;\r\n overflow: hidden;\r\n transition: all 0.3s ease;\r\n}\r\n\r\n/* 隐藏标题时的调整 */\r\n.album-header[style*=\"display: none\"],\r\n.album-header[v-if=\"false\"] {\r\n display: none !important;\r\n}\r\n\r\n.photo-album-container:has(.album-header[v-if=\"false\"]) .flipbook,\r\n.photo-album-container .flipbook {\r\n max-height: calc(100vh - 100px);\r\n}\r\n\r\n/* 移动端单页模式样式 */\r\n.flipbook.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 92vh;\r\n max-height: 92vh;\r\n margin: 0 auto;\r\n}\r\n\r\n/* PC端双页模式样式 */\r\n.flipbook.desktop-mode {\r\n width: 1600px;\r\n height: 1200px;\r\n margin: 0 auto;\r\n}\r\n\r\n.page {\r\n background: white;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n position: relative;\r\n border-radius: 5px;\r\n}\r\n\r\n.page--cover {\r\n background: linear-gradient(45deg, #e9ecef 0%, #dee2e6 50%, #ced4da 100%);\r\n color: #495057;\r\n}\r\n\r\n.cover-page, .back-cover {\r\n text-align: center;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n padding: 40px;\r\n}\r\n\r\n.cover-page h2 {\r\n font-size: 3rem;\r\n margin-bottom: 20px;\r\n text-shadow: 2px 2px 4px rgba(0,0,0,0.3);\r\n font-weight: bold;\r\n}\r\n\r\n.cover-page p {\r\n font-size: 1.5rem;\r\n opacity: 0.9;\r\n margin-bottom: 30px;\r\n}\r\n\r\n.cover-decoration {\r\n display: flex;\r\n align-items: center;\r\n gap: 15px;\r\n margin-top: 20px;\r\n}\r\n\r\n.decoration-line {\r\n width: 60px;\r\n height: 2px;\r\n background: rgba(73,80,87,0.6);\r\n border-radius: 1px;\r\n}\r\n\r\n.cover-decoration span {\r\n font-size: 1.1rem;\r\n opacity: 0.9;\r\n white-space: nowrap;\r\n}\r\n\r\n.back-cover h3 {\r\n font-size: 2.5rem;\r\n margin-bottom: 15px;\r\n text-shadow: 2px 2px 4px rgba(0,0,0,0.3);\r\n}\r\n\r\n.back-cover p {\r\n font-size: 1.2rem;\r\n opacity: 0.8;\r\n margin-bottom: 30px;\r\n}\r\n\r\n.back-decoration p {\r\n font-size: 1.5rem;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.back-decoration small {\r\n opacity: 0.7;\r\n font-size: 1rem;\r\n}\r\n\r\n.content-page {\r\n padding: 20px;\r\n height: 100%;\r\n width: 100%;\r\n box-sizing: border-box;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n}\r\n\r\n.image-container {\r\n flex: 1;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n margin-bottom: 0;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n}\r\n\r\n.image-container img {\r\n max-width: 100%;\r\n max-height: 100%;\r\n width: auto;\r\n height: auto;\r\n object-fit: contain;\r\n border-radius: 8px;\r\n box-shadow: 0 4px 12px rgba(0,0,0,0.1);\r\n transition: transform 0.3s ease;\r\n}\r\n\r\n.image-container img:hover {\r\n transform: scale(1.02);\r\n}\r\n\r\n.page-content-info {\r\n background: rgba(255,255,255,0.95);\r\n padding: 8px;\r\n border-radius: 12px;\r\n box-shadow: 0 2px 8px rgba(0,0,0,0.1);\r\n backdrop-filter: blur(10px);\r\n margin-top: 4px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.image-title {\r\n font-size: 1.2rem;\r\n font-weight: bold;\r\n color: #2c3e50;\r\n margin-bottom: 8px;\r\n text-align: center;\r\n display: block;\r\n}\r\n\r\n.image-description {\r\n font-size: 0.9rem;\r\n color: #5a6c7d;\r\n line-height: 1.4;\r\n text-align: center;\r\n margin-bottom: 10px;\r\n display: block;\r\n}\r\n\r\n.page-number {\r\n text-align: center;\r\n padding-top: 10px;\r\n border-top: 1px solid rgba(0,0,0,0.1);\r\n}\r\n\r\n.page-number span {\r\n background: linear-gradient(45deg, #667eea, #764ba2);\r\n color: white;\r\n padding: 4px 12px;\r\n border-radius: 12px;\r\n font-size: 0.8rem;\r\n font-weight: bold;\r\n}\r\n\r\n.controls {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n background: transparent;\r\n padding: 12px 20px;\r\n border-radius: 0;\r\n backdrop-filter: none;\r\n box-shadow: none;\r\n border: none;\r\n width: fit-content;\r\n margin: 20px auto;\r\n z-index: 1000;\r\n}\r\n\r\n.desktop-controls {\r\n position: relative;\r\n margin: 20px auto;\r\n}\r\n\r\n.mobile-controls {\r\n position: fixed;\r\n bottom: 20px;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n margin: 0;\r\n max-width: 90vw;\r\n}\r\n\r\n.btn {\r\n background: #333;\r\n color: white;\r\n border: 1px solid #666;\r\n padding: 8px 16px;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-size: 0.9rem;\r\n transition: all 0.3s ease;\r\n box-shadow: none;\r\n}\r\n\r\n.btn:hover:not(:disabled) {\r\n background: #555;\r\n transform: none;\r\n box-shadow: none;\r\n}\r\n\r\n.btn:disabled {\r\n opacity: 0.5;\r\n cursor: not-allowed;\r\n transform: none;\r\n}\r\n\r\n.page-indicator {\r\n color: #495057;\r\n font-size: 0.95rem;\r\n font-weight: bold;\r\n text-shadow: none;\r\n min-width: 70px;\r\n text-align: center;\r\n}\r\n\r\n/* 页码跳转容器样式 */\r\n.page-jump-container {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n background: rgba(255, 255, 255, 0.95);\r\n padding: 10px 16px;\r\n border-radius: 8px;\r\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);\r\n z-index: 1000;\r\n width: fit-content;\r\n margin: 10px auto 0;\r\n}\r\n\r\n.mobile-page-jump {\r\n padding: 8px 12px;\r\n margin: 10px auto 0;\r\n}\r\n\r\n.page-jump-input {\r\n width: 120px;\r\n padding: 8px 12px;\r\n border: 1px solid #ddd;\r\n border-radius: 6px;\r\n background: #fff;\r\n color: #333;\r\n font-size: 16px;\r\n text-align: center;\r\n outline: none;\r\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\r\n}\r\n\r\n.page-jump-input:focus {\r\n border-color: #333;\r\n box-shadow: 0 0 0 2px rgba(51, 51, 51, 0.1);\r\n}\r\n\r\n.page-jump-input::placeholder {\r\n color: #999;\r\n font-size: 0.85rem;\r\n}\r\n\r\n.btn-jump {\r\n padding: 8px 16px;\r\n white-space: nowrap;\r\n background: #333;\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 0.9rem;\r\n cursor: pointer;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.btn-jump:hover {\r\n background: #555;\r\n}\r\n\r\n/* PC端响应式设计 */\r\n@media (max-width: 1800px) and (min-width: 769px) {\r\n .flipbook.desktop-mode {\r\n width: 95vw;\r\n height: calc(95vw * 0.625);\r\n max-width: 1600px;\r\n max-height: 1000px;\r\n }\r\n}\r\n\r\n/* 移动端样式 */\r\n@media (max-width: 768px) {\r\n .photo-album-container {\r\n padding: 5px 5px 120px 5px;\r\n align-items: center;\r\n touch-action: none;\r\n overflow: hidden;\r\n }\r\n\r\n .flipbook-wrapper {\r\n width: 100%;\r\n display: flex;\r\n justify-content: center;\r\n }\r\n\r\n .flipbook.mobile-mode {\r\n width: 98vw;\r\n height: 85vh;\r\n max-width: none;\r\n max-height: 85vh;\r\n margin: 0 auto;\r\n }\r\n\r\n .album-header {\r\n width: 100%;\r\n text-align: center;\r\n }\r\n\r\n .album-header h1 {\r\n font-size: 2rem;\r\n }\r\n\r\n\r\n .btn {\r\n padding: 8px 16px;\r\n font-size: 0.9rem;\r\n }\r\n\r\n .page-indicator {\r\n font-size: 0.85rem;\r\n min-width: 120px;\r\n text-align: center;\r\n }\r\n\r\n /* 移动端页码跳转 */\r\n .page-jump-container {\r\n top: 10px;\r\n right: 10px;\r\n padding: 8px 10px;\r\n gap: 6px;\r\n }\r\n\r\n .page-jump-input {\r\n width: 100px;\r\n padding: 6px 10px;\r\n font-size: 0.85rem;\r\n }\r\n\r\n .btn-jump {\r\n padding: 6px 12px;\r\n font-size: 0.85rem;\r\n }\r\n\r\n /* 移动端图片优化 */\r\n .image-container {\r\n margin-bottom: 0;\r\n }\r\n\r\n .image-container img {\r\n max-width: 100%;\r\n max-height: 100%;\r\n border-radius: 6px;\r\n }\r\n\r\n .page-content-info {\r\n padding: 6px;\r\n margin-top: 2px;\r\n }\r\n}\r\n\r\n@media (max-width: 480px) {\r\n .flipbook.mobile-mode {\r\n width: 98vw;\r\n height: 80vh;\r\n max-height: 80vh;\r\n margin: 0 auto;\r\n }\r\n \r\n .album-header h1 {\r\n font-size: 1.8rem;\r\n }\r\n \r\n .mobile-controls {\r\n gap: 6px;\r\n padding: 8px 12px;\r\n justify-content: center;\r\n align-items: center;\r\n bottom: 15px;\r\n width: 95%;\r\n /* max-width: 95vw; */\r\n }\r\n \r\n .btn {\r\n padding: 6px 12px;\r\n font-size: 0.8rem;\r\n }\r\n \r\n .page-indicator {\r\n text-align: center;\r\n min-width: 100px;\r\n }\r\n \r\n /* 小屏幕图片优化 */\r\n .image-container {\r\n margin-bottom: 0;\r\n }\r\n \r\n .image-container img {\r\n max-width: 100%;\r\n max-height: 100%;\r\n border-radius: 4px;\r\n }\r\n \r\n .page-content-info {\r\n padding: 4px;\r\n margin-top: 1px;\r\n }\r\n}\r\n\r\n/* 设备特定的优化 */\r\n.mobile-optimized {\r\n /* 移动端优化 */\r\n -webkit-tap-highlight-color: transparent;\r\n -webkit-touch-callout: none;\r\n -webkit-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n.desktop-optimized {\r\n /* PC端优化 */\r\n cursor: pointer;\r\n}\r\n\r\n/* 翻页按钮在不同设备上的样式 */\r\n.btn {\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.btn:hover:not(:disabled) {\r\n background: #555;\r\n transform: none;\r\n box-shadow: none;\r\n}\r\n\r\n/* 缩放提示样式 */\r\n.zoom-hint {\r\n position: fixed;\r\n top: 20px;\r\n right: 20px;\r\n background: rgba(0, 0, 0, 0.85);\r\n color: white;\r\n padding: 10px 16px;\r\n border-radius: 25px;\r\n font-size: 0.85rem;\r\n opacity: 0;\r\n transform: translateY(-10px);\r\n transition: all 0.3s ease;\r\n z-index: 10001;\r\n pointer-events: none;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.zoom-hint.show {\r\n opacity: 1;\r\n transform: translateY(0);\r\n pointer-events: auto;\r\n}\r\n\r\n.zoom-info {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n gap: 12px;\r\n}\r\n\r\n.zoom-info span {\r\n font-weight: 500;\r\n}\r\n\r\n.btn-reset-zoom {\r\n background: rgba(255, 255, 255, 0.2);\r\n color: white;\r\n border: 1px solid rgba(255, 255, 255, 0.3);\r\n padding: 4px 12px;\r\n border-radius: 15px;\r\n font-size: 0.75rem;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n font-weight: 600;\r\n white-space: nowrap;\r\n}\r\n\r\n.btn-reset-zoom:hover {\r\n background: rgba(255, 255, 255, 0.3);\r\n border-color: rgba(255, 255, 255, 0.5);\r\n transform: scale(1.05);\r\n}\r\n\r\n.btn-reset-zoom:active {\r\n transform: scale(0.95);\r\n}\r\n\r\n/* 控件显示/隐藏动画 */\r\n.controls {\r\n opacity: 0;\r\n visibility: hidden;\r\n transform: translateY(20px);\r\n transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;\r\n display: flex;\r\n background: rgba(255, 255, 255, 0.9);\r\n border-radius: 8px;\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.controls.controls-visible {\r\n opacity: 1;\r\n visibility: visible;\r\n transform: translateY(0);\r\n}\r\n\r\n.mobile-controls {\r\n position: fixed !important;\r\n bottom: 76px !important;\r\n left: 50% !important;\r\n transform: translateX(-50%) !important;\r\n z-index: 10000 !important;\r\n}\r\n\r\n.desktop-controls {\r\n position: relative !important;\r\n margin: 20px auto !important;\r\n}\r\n\r\n/* 目录按钮样式 */\r\n.catalogue-button {\r\n position: fixed;\r\n z-index: 9999999999;\r\n opacity: 0;\r\n visibility: hidden;\r\n transform: translateX(-20px);\r\n transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;\r\n user-select: none;\r\n}\r\n\r\n.catalogue-button.catalogue-visible {\r\n opacity: 1;\r\n visibility: visible;\r\n transform: translateX(0);\r\n}\r\n\r\n.catalogue-button.dragging {\r\n transform: scale(1.05);\r\n /* box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); */\r\n z-index: 999999;\r\n}\r\n\r\n\r\n.btn-catalogue {\r\n background: linear-gradient(45deg, #1989fa, #0066cc);\r\n color: white;\r\n border: 2px solid rgba(255, 255, 255, 0.3);\r\n padding: 10px 16px;\r\n border-radius: 20px;\r\n font-size: 0.9rem;\r\n font-weight: 600;\r\n box-shadow: 0 4px 12px rgba(25, 137, 250, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);\r\n transition: all 0.2s ease;\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n cursor: grab;\r\n white-space: nowrap;\r\n pointer-events: all;\r\n}\r\n\r\n.catalogue-button.dragging .btn-catalogue {\r\n cursor: grabbing;\r\n /* background: linear-gradient(45deg, #0066cc, #004499); */\r\n}\r\n\r\n.btn-catalogue:active {\r\n transform: scale(0.98);\r\n}\r\n\r\n/* 拖拽手柄样式 */\r\n.btn-catalogue .drag-handle {\r\n color: rgba(255, 255, 255, 0.7);\r\n font-size: 14px;\r\n margin-right: 2px;\r\n user-select: none;\r\n}\r\n\r\n.catalogue-icon {\r\n font-size: 1rem;\r\n flex-shrink: 0;\r\n}\r\n\r\n.catalogue-text {\r\n font-size: 0.9rem;\r\n flex-shrink: 0;\r\n}\r\n\r\n.btn-catalogue:hover {\r\n background: linear-gradient(45deg, #0066cc, #004499);\r\n transform: translateY(-2px);\r\n box-shadow: 0 6px 16px rgba(25, 137, 250, 0.4);\r\n}\r\n\r\n.btn-catalogue:hover .drag-handle {\r\n color: rgba(255, 255, 255, 0.9);\r\n}\r\n\r\n/* PC端缩放工具栏 */\r\n.zoom-toolbar {\r\n position: fixed;\r\n bottom: 20px;\r\n right: 20px;\r\n display: flex;\r\n gap: 8px;\r\n z-index: 10000;\r\n}\r\n\r\n.btn-zoom {\r\n width: 40px;\r\n height: 40px;\r\n background: #333;\r\n color: white;\r\n border: 1px solid #666;\r\n border-radius: 4px;\r\n font-size: 1.2rem;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.btn-zoom:hover:not(:disabled) {\r\n background: #555;\r\n}\r\n\r\n.btn-zoom:disabled {\r\n opacity: 0.3;\r\n cursor: not-allowed;\r\n}\r\n\r\n/* 移动端按钮优化 */\r\n@media (max-width: 768px) {\r\n .btn:active {\r\n background: #555;\r\n transform: none;\r\n }\r\n\r\n .btn:hover:not(:disabled) {\r\n background: #555;\r\n transform: none;\r\n }\r\n\r\n .btn-catalogue {\r\n padding: 8px 12px;\r\n font-size: 0.8rem;\r\n border-radius: 16px;\r\n }\r\n\r\n .btn-catalogue .drag-handle {\r\n font-size: 16px;\r\n }\r\n\r\n .catalogue-icon {\r\n font-size: 0.9rem;\r\n }\r\n\r\n .catalogue-text {\r\n font-size: 0.8rem;\r\n }\r\n\r\n .zoom-hint {\r\n top: 10px;\r\n right: 10px;\r\n font-size: 0.75rem;\r\n }\r\n}\r\n\r\n/* 加载状态样式 */\r\n.loading-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(10px);\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 9999;\r\n animation: fadeIn 0.3s ease-out;\r\n}\r\n\r\n.loading-container {\r\n text-align: center;\r\n padding: 40px;\r\n background: white;\r\n border-radius: 20px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);\r\n max-width: 400px;\r\n width: 90%;\r\n}\r\n\r\n.loading-spinner {\r\n position: relative;\r\n width: 80px;\r\n height: 80px;\r\n margin: 0 auto 30px;\r\n}\r\n\r\n.spinner-ring {\r\n position: absolute;\r\n width: 100%;\r\n height: 100%;\r\n border: 3px solid transparent;\r\n border-top: 3px solid #007bff;\r\n border-radius: 50%;\r\n animation: spin 1.2s linear infinite;\r\n}\r\n\r\n.spinner-ring:nth-child(2) {\r\n width: 60px;\r\n height: 60px;\r\n top: 10px;\r\n left: 10px;\r\n border-top-color: #28a745;\r\n animation-delay: -0.4s;\r\n}\r\n\r\n.spinner-ring:nth-child(3) {\r\n width: 40px;\r\n height: 40px;\r\n top: 20px;\r\n left: 20px;\r\n border-top-color: #ffc107;\r\n animation-delay: -0.8s;\r\n}\r\n\r\n@keyframes spin {\r\n 0% { transform: rotate(0deg); }\r\n 100% { transform: rotate(360deg); }\r\n}\r\n\r\n.loading-text h3 {\r\n color: #333;\r\n font-size: 1.5rem;\r\n margin-bottom: 10px;\r\n font-weight: 600;\r\n}\r\n\r\n.loading-text p {\r\n color: #666;\r\n font-size: 1rem;\r\n margin-bottom: 20px;\r\n line-height: 1.5;\r\n}\r\n\r\n.loading-progress {\r\n width: 100%;\r\n height: 4px;\r\n background: #f0f0f0;\r\n border-radius: 2px;\r\n overflow: hidden;\r\n margin-top: 20px;\r\n}\r\n\r\n.progress-bar {\r\n height: 100%;\r\n background: linear-gradient(90deg, #007bff, #28a745, #ffc107);\r\n background-size: 200% 100%;\r\n animation: progressMove 2s ease-in-out infinite;\r\n border-radius: 2px;\r\n}\r\n\r\n@keyframes progressMove {\r\n 0% {\r\n transform: translateX(-100%);\r\n background-position: 200% 0;\r\n }\r\n 100% {\r\n transform: translateX(100%);\r\n background-position: -200% 0;\r\n }\r\n}\r\n\r\n/* 错误状态样式 */\r\n.error-container {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(10px);\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 9999;\r\n animation: fadeIn 0.3s ease-out;\r\n}\r\n\r\n.error-content {\r\n text-align: center;\r\n padding: 40px;\r\n background: white;\r\n border-radius: 20px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);\r\n max-width: 400px;\r\n width: 90%;\r\n}\r\n\r\n.error-icon {\r\n font-size: 4rem;\r\n margin-bottom: 20px;\r\n display: block;\r\n}\r\n\r\n.error-content h3 {\r\n color: #dc3545;\r\n font-size: 1.5rem;\r\n margin-bottom: 15px;\r\n font-weight: 600;\r\n}\r\n\r\n.error-content p {\r\n color: #666;\r\n font-size: 1rem;\r\n margin-bottom: 30px;\r\n line-height: 1.5;\r\n}\r\n\r\n.retry-btn {\r\n background: linear-gradient(45deg, #007bff, #0056b3);\r\n color: white;\r\n border: none;\r\n padding: 12px 30px;\r\n border-radius: 25px;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n cursor: pointer;\r\n transition: all 0.3s ease;\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 8px;\r\n box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);\r\n}\r\n\r\n.retry-btn:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);\r\n}\r\n\r\n.retry-btn:active {\r\n transform: translateY(0);\r\n}\r\n\r\n.retry-icon {\r\n display: inline-block;\r\n animation: rotateIcon 0.5s ease-in-out;\r\n}\r\n\r\n.retry-btn:hover .retry-icon {\r\n animation: rotateIcon 0.5s ease-in-out infinite;\r\n}\r\n\r\n@keyframes rotateIcon {\r\n from { transform: rotate(0deg); }\r\n to { transform: rotate(360deg); }\r\n}\r\n\r\n/* 成功提示样式 */\r\n.success-toast {\r\n position: fixed;\r\n top: 30px;\r\n right: 30px;\r\n background: linear-gradient(45deg, #28a745, #20c997);\r\n color: white;\r\n padding: 15px 25px;\r\n border-radius: 25px;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n box-shadow: 0 8px 25px rgba(40, 167, 69, 0.3);\r\n z-index: 10000;\r\n opacity: 0;\r\n transform: translateX(100px);\r\n transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n.success-toast.show {\r\n opacity: 1;\r\n transform: translateX(0);\r\n}\r\n\r\n.success-icon {\r\n font-size: 1.2rem;\r\n}\r\n\r\n@keyframes fadeIn {\r\n from {\r\n opacity: 0;\r\n transform: scale(0.9);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: scale(1);\r\n }\r\n}\r\n\r\n/* 移动端适配 */\r\n/* 翻页模式选择器样式 */\r\n.flip-mode-selector {\r\n position: fixed;\r\n top: 20px;\r\n left: 20px;\r\n z-index: 10000;\r\n}\r\n\r\n.mobile-flip-mode-selector {\r\n top: 15px;\r\n left: 15px;\r\n}\r\n\r\n.btn-flip-mode {\r\n background: linear-gradient(45deg, #6c5ce7, #a29bfe);\r\n color: white;\r\n border: 2px solid rgba(255, 255, 255, 0.3);\r\n padding: 10px 16px;\r\n border-radius: 20px;\r\n font-size: 0.9rem;\r\n font-weight: 600;\r\n box-shadow: 0 4px 12px rgba(108, 92, 231, 0.4);\r\n transition: all 0.2s ease;\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n cursor: pointer;\r\n}\r\n\r\n.btn-flip-mode:hover {\r\n background: linear-gradient(45deg, #5f4ed5, #8c7ae6);\r\n transform: translateY(-2px);\r\n box-shadow: 0 6px 16px rgba(108, 92, 231, 0.5);\r\n}\r\n\r\n.flip-mode-icon {\r\n font-size: 1rem;\r\n}\r\n\r\n.flip-mode-text {\r\n font-size: 0.85rem;\r\n}\r\n\r\n.flip-mode-menu {\r\n position: absolute;\r\n top: 100%;\r\n left: 0;\r\n margin-top: 8px;\r\n background: white;\r\n border-radius: 12px;\r\n box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);\r\n overflow: hidden;\r\n min-width: 150px;\r\n animation: slideDown 0.2s ease-out;\r\n}\r\n\r\n@keyframes slideDown {\r\n from {\r\n opacity: 0;\r\n transform: translateY(-10px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n.flip-mode-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 12px 16px;\r\n cursor: pointer;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.flip-mode-item:hover {\r\n background: #f5f5f5;\r\n}\r\n\r\n.flip-mode-item-active {\r\n background: linear-gradient(45deg, #6c5ce7, #a29bfe);\r\n color: white;\r\n}\r\n\r\n.flip-mode-item-active:hover {\r\n background: linear-gradient(45deg, #5f4ed5, #8c7ae6);\r\n}\r\n\r\n.flip-mode-item-icon {\r\n font-size: 1.1rem;\r\n}\r\n\r\n.flip-mode-item-label {\r\n font-size: 0.9rem;\r\n font-weight: 500;\r\n}\r\n\r\n/* 滑动模式样式 */\r\n.slide-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: hidden;\r\n position: relative;\r\n}\r\n\r\n.slide-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.slide-container {\r\n display: flex;\r\n height: 100%;\r\n will-change: transform;\r\n}\r\n\r\n.slide-page {\r\n flex: 0 0 100%;\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n}\r\n\r\n.slide-page-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n}\r\n\r\n.slide-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n/* 淡入淡出模式样式 */\r\n.fade-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: hidden;\r\n position: relative;\r\n}\r\n\r\n.fade-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.fade-page-container {\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n}\r\n\r\n.fade-page-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n}\r\n\r\n.fade-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n.fade-page-enter-active,\r\n.fade-page-leave-active {\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.fade-page-enter,\r\n.fade-page-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n/* 垂直滚动模式样式 */\r\n.scroll-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow-y: auto;\r\n overflow-x: hidden;\r\n position: relative;\r\n scroll-behavior: smooth;\r\n}\r\n\r\n.scroll-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.scroll-container {\r\n width: 100%;\r\n}\r\n\r\n.scroll-page {\r\n width: 100%;\r\n min-height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n padding: 20px 0;\r\n box-sizing: border-box;\r\n}\r\n\r\n.scroll-page-image {\r\n max-width: 100%;\r\n max-height: none;\r\n width: auto;\r\n}\r\n\r\n.scroll-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n min-height: 300px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n/* 滚动条样式 */\r\n.scroll-viewer::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n.scroll-viewer::-webkit-scrollbar-track {\r\n background: #f1f1f1;\r\n border-radius: 4px;\r\n}\r\n\r\n.scroll-viewer::-webkit-scrollbar-thumb {\r\n background: #c1c1c1;\r\n border-radius: 4px;\r\n}\r\n\r\n.scroll-viewer::-webkit-scrollbar-thumb:hover {\r\n background: #a1a1a1;\r\n}\r\n\r\n/* 截断特效模式样式 */\r\n.clip-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: hidden;\r\n position: relative;\r\n perspective: 1200px;\r\n}\r\n\r\n.clip-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.clip-pages-wrapper {\r\n width: 100%;\r\n height: 100%;\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.clip-page {\r\n position: absolute;\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n}\r\n\r\n.clip-page-current {\r\n z-index: 2;\r\n clip-path: inset(0 0 0 0);\r\n}\r\n\r\n.clip-page-next {\r\n z-index: 1;\r\n opacity: 0.5;\r\n transform: scale(0.95);\r\n filter: blur(2px);\r\n}\r\n\r\n.clip-page-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n border-radius: 8px;\r\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);\r\n}\r\n\r\n.clip-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n/* 截断动画 */\r\n.clip-current-enter-active {\r\n animation: clipIn 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.clip-current-leave-active {\r\n animation: clipOut 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n@keyframes clipIn {\r\n 0% {\r\n clip-path: inset(0 100% 0 0);\r\n transform: translateX(50px);\r\n opacity: 0;\r\n }\r\n 100% {\r\n clip-path: inset(0 0 0 0);\r\n transform: translateX(0);\r\n opacity: 1;\r\n }\r\n}\r\n\r\n@keyframes clipOut {\r\n 0% {\r\n clip-path: inset(0 0 0 0);\r\n transform: translateX(0);\r\n opacity: 1;\r\n }\r\n 100% {\r\n clip-path: inset(0 0 0 100%);\r\n transform: translateX(-50px);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n/* 卡片风格模式样式 */\r\n.card-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: visible;\r\n position: relative;\r\n perspective: 1500px;\r\n}\r\n\r\n.card-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.card-stack {\r\n width: 100%;\r\n height: 100%;\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n transform-style: preserve-3d;\r\n}\r\n\r\n.card-transition-group {\r\n width: 100%;\r\n height: 100%;\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.card-item {\r\n position: absolute;\r\n width: 70%;\r\n height: 90%;\r\n transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n transform-style: preserve-3d;\r\n cursor: pointer;\r\n}\r\n\r\n.card-content {\r\n width: 100%;\r\n height: 100%;\r\n background: white;\r\n border-radius: 16px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 8px 25px rgba(0, 0, 0, 0.2);\r\n overflow: hidden;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n position: relative;\r\n}\r\n\r\n.card-item-current .card-content {\r\n box-shadow: 0 30px 80px rgba(0, 0, 0, 0.35), 0 15px 40px rgba(0, 0, 0, 0.25);\r\n}\r\n\r\n.card-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n}\r\n\r\n.card-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n.card-page-number {\r\n position: absolute;\r\n bottom: 15px;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n background: rgba(0, 0, 0, 0.7);\r\n color: white;\r\n padding: 6px 16px;\r\n border-radius: 20px;\r\n font-size: 0.85rem;\r\n font-weight: 500;\r\n}\r\n\r\n.card-item-prev .card-page-number,\r\n.card-item-next .card-page-number {\r\n opacity: 0;\r\n}\r\n\r\n/* 卡片切换动画 */\r\n.card-flip-enter-active,\r\n.card-flip-leave-active {\r\n transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.card-flip-enter {\r\n opacity: 0;\r\n transform: translateX(100%) scale(0.8) rotateY(-30deg);\r\n}\r\n\r\n.card-flip-leave-to {\r\n opacity: 0;\r\n transform: translateX(-100%) scale(0.8) rotateY(30deg);\r\n}\r\n\r\n/* 卡片悬停效果 */\r\n.card-item-current:hover .card-content {\r\n box-shadow: 0 35px 90px rgba(0, 0, 0, 0.4), 0 18px 50px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n/* 移动端卡片样式 */\r\n@media (max-width: 768px) {\r\n .card-item {\r\n width: 85%;\r\n height: 85%;\r\n }\r\n \r\n .card-item-prev,\r\n .card-item-next {\r\n display: none;\r\n }\r\n \r\n .card-content {\r\n border-radius: 12px;\r\n }\r\n \r\n .card-page-number {\r\n font-size: 0.75rem;\r\n padding: 4px 12px;\r\n }\r\n \r\n .clip-page-next {\r\n display: none;\r\n }\r\n}\r\n\r\n/* 移动端翻页模式选择器样式 */\r\n@media (max-width: 768px) {\r\n .btn-flip-mode {\r\n padding: 8px 12px;\r\n font-size: 0.8rem;\r\n border-radius: 16px;\r\n }\r\n \r\n .flip-mode-icon {\r\n font-size: 0.9rem;\r\n }\r\n \r\n .flip-mode-text {\r\n font-size: 0.75rem;\r\n }\r\n \r\n .flip-mode-menu {\r\n min-width: 130px;\r\n }\r\n \r\n .flip-mode-item {\r\n padding: 10px 14px;\r\n }\r\n \r\n .flip-mode-item-icon {\r\n font-size: 1rem;\r\n }\r\n \r\n .flip-mode-item-label {\r\n font-size: 0.8rem;\r\n }\r\n}\r\n\r\n@media (max-width: 768px) {\r\n .loading-container,\r\n .error-content {\r\n padding: 30px 20px;\r\n margin: 20px;\r\n }\r\n \r\n .loading-spinner {\r\n width: 60px;\r\n height: 60px;\r\n }\r\n \r\n .spinner-ring:nth-child(2) {\r\n width: 45px;\r\n height: 45px;\r\n top: 7.5px;\r\n left: 7.5px;\r\n }\r\n \r\n .spinner-ring:nth-child(3) {\r\n width: 30px;\r\n height: 30px;\r\n top: 15px;\r\n left: 15px;\r\n }\r\n \r\n .loading-text h3,\r\n .error-content h3 {\r\n font-size: 1.3rem;\r\n }\r\n \r\n .loading-text p,\r\n .error-content p {\r\n font-size: 0.9rem;\r\n }\r\n \r\n .success-toast {\r\n top: 20px;\r\n right: 20px;\r\n left: 20px;\r\n right: 20px;\r\n font-size: 0.9rem;\r\n padding: 12px 20px;\r\n }\r\n \r\n .retry-btn {\r\n padding: 10px 25px;\r\n font-size: 0.9rem;\r\n }\r\n}\r\n\r\n</style>\r\n","import mod from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=script&lang=js\"","// extracted by mini-css-extract-plugin\nexport {};","export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../node_modules/@vue/vue-loader-v15/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=style&index=0&id=23d695ae&prod&scoped=true&lang=css\"","import { render, staticRenderFns } from \"./BookReader.vue?vue&type=template&id=23d695ae&scoped=true\"\nimport script from \"./BookReader.vue?vue&type=script&lang=js\"\nexport * from \"./BookReader.vue?vue&type=script&lang=js\"\nimport style0 from \"./BookReader.vue?vue&type=style&index=0&id=23d695ae&prod&scoped=true&lang=css\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"23d695ae\",\n null\n \n)\n\nexport default component.exports","import BookReader from './components/BookReader.vue'\r\nimport BookCatalogueDrawer from './components/BookCatalogueDrawer.vue'\r\n\r\nconst components = {\r\n BookReader,\r\n BookCatalogueDrawer\r\n}\r\n\r\nconst install = function(Vue) {\r\n if (install.installed) return\r\n install.installed = true\r\n Object.keys(components).forEach(key => {\r\n Vue.component(key, components[key])\r\n })\r\n}\r\n\r\nif (typeof window !== 'undefined' && window.Vue) {\r\n install(window.Vue)\r\n}\r\n\r\nexport default {\r\n install,\r\n BookReader,\r\n BookCatalogueDrawer\r\n}\r\n\r\nexport {\r\n BookReader,\r\n BookCatalogueDrawer\r\n}\r\n","import './setPublicPath'\nimport mod from '~entry'\nexport default mod\nexport * from '~entry'\n"],"names":[],"ignoreList":[],"sourceRoot":""}
1
+ {"version":3,"file":"vue-book-reader.common.js","mappings":";;UAAA;UACA;;;;;WCDA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA,E;;;;;WCPA,8CAA8C,yD;;;;;WCA9C;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D,E;;;;;WCNA,2B;;;;;;;;;;;;;;;;ACAA;AACA;;AAEA;AACA;AACA,MAAM,KAAuC,EAAE;AAAA,yBAQ5C;;AAEH;AACA;AACA,IAAI,qBAAuB;AAC3B;AACA;;AAEA;AACA,kDAAe,IAAI;;;ACtBnB,+BAA+B,6BAA6B,iBAAiB,wCAAwC,iLAAiL,GAAG,MAAK,EAAE,CAAgE,uCAAuC,wCAAwC,oBAAoB,cAAc,cAAc,eAAe,MAAM,eAAe,MAAM,MAAM,oEAAoE,uDAAuD,8BAA8B,uDAAuD,uDAAuD,2HAA2H,6OAA6O,KAAK,sEAAsE,sBAAsB,4BAA4B,iCAAiC,EAAE,kBAAkB,yBAAyB,uCAAuC,+HAA+H,sHAAsH,YAAY,8DAA8D,yCAAyC,iBAAiB,2CAA2C,qDAAqD,mBAAmB,sCAAsC,qBAAqB,YAAY,qCAAqC,sBAAsB,2CAA2C,yHAAyH,mBAAmB,OAAO,oCAAoC,YAAY,sDAAsD,6CAA6C,qCAAqC,+CAA+C,YAAY,oCAAoC,kEAAkE,iIAAiI,mCAAmC,YAAY,+BAA+B,yCAAyC,iBAAiB,2EAA2E,mBAAmB,uCAAuC,qBAAqB,YAAY,sCAAsC,sBAAsB,2CAA2C,yHAAyH,YAAY,iCAAiC,mBAAmB,OAAO,uBAAuB,YAAY,2EAA2E,6CAA6C,qCAAqC,+CAA+C,YAAY,oCAAoC,qEAAqE,uCAAuC,yCAAyC,qCAAqC,2CAA2C,YAAY,oCAAoC,yEAAyE,yHAAyH,YAAY,yBAAyB,yBAAyB,2CAA2C,gCAAgC,gDAAgD,iBAAiB;AAC3pI;AACA;AACA;AACA,aAAa,yCAAyC,YAAY,2BAA2B,uBAAuB,gCAAgC,yBAAyB,YAAY,+BAA+B,gCAAgC,+BAA+B,0EAA0E,2CAA2C,+BAA+B,0GAA0G,KAAK,yBAAyB,4BAA4B,eAAe,+BAA+B,sBAAsB,iCAAiC,6BAA6B,2rBAA2rB,+BAA+B,uBAAuB,6DAA6D,yCAAyC,4CAA4C,eAAe,oCAAoC,gCAAgC,aAAa,6BAA6B,gDAAgD,6BAA6B,yEAAyE,6BAA6B,uCAAuC,iBAAiB,oDAAoD,sDAAsD,KAAK,yBAAyB,wCAAwC,aAAa,kCAAkC,yCAAyC,mCAAmC,iCAAiC,mCAAmC;AAC3yE;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK,MAAM,6KAA6K,4BAA4B,eAAe,oCAAoC,2BAA2B,aAAa,6BAA6B,+BAA+B,gCAAgC,8BAA8B,YAAY,wBAAwB,uFAAuF,iCAAiC,uBAAuB,+BAA+B,2BAA2B,eAAe,8BAA8B,gCAAgC,KAAK,qBAAqB,6BAA6B,8BAA8B,8BAA8B,KAAK,oBAAoB,0CAA0C,8BAA8B,6DAA6D,8BAA8B,YAAY,4BAA4B,YAAY,yBAAyB,yHAAyH,4BAA4B,4BAA4B,aAAa,yBAAyB,yGAAyG,oCAAoC,+BAA+B,4DAA4D,OAAO,8EAA8E,KAAK,6HAA6H,QAAQ,yDAAyD,4BAA4B,mCAAmC;AACr3D;AACA,mCAAmC,6BAA6B,iBAAiB,gCAAgC,YAAY,8BAA8B,YAAY,2BAA2B,YAAY,2BAA2B,YAAY,2BAA2B,cAAc,2BAA2B,4EAA4E,+BAA+B,YAAY,2BAA2B;AAC3c,CAAC;;;;;;AEdD;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,mCAAmC;AACpE,iCAAiC,uBAAuB;;AAExD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,kBAAkB,QAAQ;AAC1B;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,kBAAkB,OAAO;AACzB;AACA,oBAAoB,OAAO;AAC3B;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA,SAAS,oBAAQ;AACjB;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEoO;;;ACnRpO;AACA;AACA;AACA;AACA;AACA;;AAEsG;;AAEtG;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,MAAM;AACN,eAAe,QAAQ;AACvB;AACA;;AAEA;AACA;AACA;;AAEA;AACA,oBAAoB,QAAQ;AAC5B;;AAEA;AACA,yBAAyB,WAAW;AACpC;;AAEA;AACA;AACA;;AAEA;AACA,yBAAyB,SAAS;AAClC;;AAEA;AACA,yBAAyB,WAAW;AACpC;;AAEA;AACA,yBAAyB,OAAO;AAChC;;AAEA;AACA,WAAW,oBAAQ;AACnB;;AAEA;AACA,CAAC;;AAED;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,OAAO;AACP;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,KAAK;AACL,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,YAAY;AACZ;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA,UAAU;AACV;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4CAA4C,gCAAgC;AAC5E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,OAAO;AACP,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,eAAe;AACf;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA,eAAe;AACf;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA,QAAQ;;AAER;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;;AAER;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA,4EAA4E,uCAAuC;AACnH,gCAAgC;AAChC;AACA;AACA,6FAA6F,sCAAsC;AACnI;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA,cAAc;AACd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,SAAS;AACT,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,SAAS;AACT,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0FAA0F;AAC1F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA,oDAAoD,4BAA4B;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB;AAClB;AACA,kBAAkB;AAClB;AACA,kBAAkB;AAClB;AACA;AACA;;AAEA;AACA;;AAEA;AACA,kCAAkC,aAAa,0BAA0B,wBAAwB;AACjG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,wBAAwB;AAC7B;AACA;AACA,KAAK,UAAU,wDAAwD,MAAM,wPAAwP,YAAY,0CAA0C,wCAAwC,EAAE,YAAY,0CAA0C,8CAA8C,MAAM,sBAAsB,wBAAwB,2CAA2C,+CAA+C,MAAM,uBAAuB,wBAAwB,SAAS,+DAA+D,EAAE,+BAA+B;AACrzB;AACA;AACA;AACA;AACA,WAAW,SAAS,6CAA6C,KAAK,wBAAwB,kCAAkC,qDAAqD;AACrL;AACA;AACA;AACA;AACA,WAAW,SAAS,8CAA8C,KAAK,wBAAwB,kCAAkC,iCAAiC,SAAS,2BAA2B,EAAE;AACxM;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,sCAAsC,iBAAiB;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,EAAE,YAAY,aAAa,kFAAkF,kCAAkC,2BAA2B,EAAE,IAAI,2BAA2B;AACxN;AACA;AACA;AACA;AACA;AACA,WAAW,MAAM,2FAA2F;AAC5G;;AAEA;AACA;AACA,mBAAmB;AACnB,kCAAkC,oCAAoC,iCAAiC,WAAW,YAAY,gCAAgC,gBAAgB,+CAA+C,gBAAgB,qCAAqC,kBAAkB,WAAW,YAAY,0BAA0B,iBAAiB,gCAAgC,kBAAkB,UAAU,YAAY,MAAM,iBAAiB,qCAAqC,OAAO,sCAAsC,QAAQ,+BAA+B,kBAAkB,iBAAiB,uBAAuB,kBAAkB,2BAA2B,0BAA0B,kBAAkB,MAAM,OAAO,4BAA4B,2BAA2B,6BAA6B,gCAAgC,sBAAsB,oCAAoC,WAAW,YAAY,qCAAqC;;AAEx8B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,MAAM,kEAAkE;AACxE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEwC;;;AC/yCxC,IAAI,mEAAM,qBAAqB,6BAA6B,iBAAiB,yCAAyC,0BAA0B,iCAAiC,yBAAyB,qBAAqB,uCAAuC,4BAA4B,UAAU,wBAAwB,EAAE,YAAY,4BAA4B,WAAW,2BAA2B,8BAA8B,+BAA+B,yBAAyB,aAAa,yBAAyB,8BAA8B,gCAAgC,cAAc,aAAa,oFAAoF,uCAAuC,oCAAoC,WAAW,4BAA4B,KAAK,0BAA0B,kCAAkC,sCAAsC,uEAAuE,eAAe,2BAA2B,8BAA8B,+BAA+B,6BAA6B,0BAA0B,gCAAgC,YAAY,8BAA8B,uDAAuD,8BAA8B,YAAY,yBAAyB,yDAAyD,+BAA+B,4BAA4B,qFAAqF,6BAA6B,iDAAiD,4BAA4B,iBAAiB,MAAM,SAAS,iBAAiB,WAAW,KAAK,8BAA8B,EAAE,eAAe,8BAA8B;AAChwD;AACA,IAAI,4EAAe;;;;;ACqEnB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,+EAAe;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,CAAC;;;ACpY6I,CAAC,0FAAe,0CAAG,EAAC,C;;ACAlK;;;;;AEAA;;AAEA;AACA;AACA;;AAEe,SAAS,sCAAkB;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;AC/F6G;AACvC;AACL;AACjE,CAA2G;;;AAG3G;AACmG;AACnG,gBAAgB,sCAAU;AAC1B,EAAE,qDAAM;AACR,EAAE,mEAAM;AACR,EAAE,4EAAe;AACjB;AACA;AACA;AACA;AACA;AACA;;AAEA,wDAAe,iB;;;ACsSyB;AACmB;;AAE3D,sEAAe;AACf;AACA;AACA,YAAY;AACZ,uBAAuB;AACvB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;;;AC58CoI,CAAC,iFAAe,iCAAG,EAAC,C;;ACAzJ;;;;;AEAoG;AACvC;AACL;AACxD,CAAkG;;;AAGlG;AACmG;AACnG,IAAI,oBAAS,GAAG,sCAAU;AAC1B,EAAE,4CAAM;AACR,EAAE,MAAM;AACR,EAAE,eAAe;AACjB;AACA;AACA;AACA;AACA;AACA;;AAEA,+CAAe,oBAAS,Q;;ACnB4B;AACkB;AACtE;AACA;AACA,YAAY;AACZ,qBAAqB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,0CAAe;AACf;AACA,YAAY;AACZ,qBAAqB;AACrB,CAAC;AACD;AAIC;;;AC7BuB;AACA;AACxB,8CAAe,KAAG;AACI","sources":["webpack://@airhang/vue-book-reader/webpack/bootstrap","webpack://@airhang/vue-book-reader/webpack/runtime/define property getters","webpack://@airhang/vue-book-reader/webpack/runtime/hasOwnProperty shorthand","webpack://@airhang/vue-book-reader/webpack/runtime/make namespace object","webpack://@airhang/vue-book-reader/webpack/runtime/publicPath","webpack://@airhang/vue-book-reader/./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?c4a3","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?e336","webpack://@airhang/vue-book-reader/./node_modules/rematrix/dist/rematrix.es.js","webpack://@airhang/vue-book-reader/./node_modules/flipbook-vue/dist/vue2/flipbook.mjs","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?ef5d","webpack://@airhang/vue-book-reader/src/components/BookCatalogueDrawer.vue","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?e206","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?4baa","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue?5d6d","webpack://@airhang/vue-book-reader/./node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js","webpack://@airhang/vue-book-reader/./src/components/BookCatalogueDrawer.vue","webpack://@airhang/vue-book-reader/src/components/BookReader.vue","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?0add","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?c578","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue?b24c","webpack://@airhang/vue-book-reader/./src/components/BookReader.vue","webpack://@airhang/vue-book-reader/./src/index.js","webpack://@airhang/vue-book-reader/./node_modules/@vue/cli-service/lib/commands/build/entry-lib.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.p = \"\";","/* eslint-disable no-var */\n// This file is imported into lib/wc client bundles.\n\nif (typeof window !== 'undefined') {\n var currentScript = window.document.currentScript\n if (process.env.NEED_CURRENTSCRIPT_POLYFILL) {\n var getCurrentScript = require('@soda/get-current-script')\n currentScript = getCurrentScript()\n\n // for backward compatibility, because previously we directly included the polyfill\n if (!('currentScript' in document)) {\n Object.defineProperty(document, 'currentScript', { get: getCurrentScript })\n }\n }\n\n var src = currentScript && currentScript.src.match(/(.+\\/)[^/]+\\.js(\\?.*)?$/)\n if (src) {\n __webpack_public_path__ = src[1] // eslint-disable-line\n }\n}\n\n// Indicate to webpack that this file can be concatenated\nexport default null\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"photo-album-container\",on:{\"touchstart\":_vm.onContainerTouchStart,\"touchmove\":_vm.onContainerTouchMove,\"touchend\":_vm.onContainerTouchEnd,\"dblclick\":_vm.onContainerDoubleClick,\"click\":_vm.onContentClick}},[(false)?_c('div',{staticClass:\"album-header\"},[_c('h1',[_vm._v(\"氪氪\")])]):_vm._e(),(_vm.contentReady)?_c('div',{staticClass:\"flipbook-wrapper\",style:({ transform: `scale(${_vm.zoomScale}) translate(${_vm.translateX}px, ${_vm.translateY}px)` }),on:{\"dblclick\":_vm.onFlipbookDoubleClick,\"!touchstart\":function($event){return _vm.onFlipbookTouchStart.apply(null, arguments)},\"!touchend\":function($event){return _vm.onFlipbookTouchEnd.apply(null, arguments)}}},[(_vm.flipMode === 'flip' && _vm.flag)?_c('flipbook',{ref:\"flipbook\",class:['flipbook', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized'],attrs:{\"pages\":_vm.pages,\"pagesHiRes\":_vm.pagesHiRes,\"startPage\":_vm.startPage,\"zooms\":null,\"ambient-light\":0.6,\"gloss\":0.4,\"single-page\":_vm.isMobile,\"click-to-flip\":false,\"wheel-to-flip\":!_vm.isMobile,\"swipe-to-flip\":true,\"center-pages\":true},on:{\"flip-left-end\":_vm.onFlipLeftEnd,\"flip-right-end\":_vm.onFlipRightEnd},scopedSlots:_vm._u([{key:\"default\",fn:function({ page, canFlipLeft, canFlipRight }){return undefined}}],null,false,2449366912)}):(_vm.flipMode === 'slide')?_c('div',{ref:\"slideViewer\",class:['slide-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized'],on:{\"touchstart\":_vm.onSlideViewerTouchStart,\"touchmove\":_vm.onSlideViewerTouchMove,\"touchend\":_vm.onSlideViewerTouchEnd}},[_c('div',{staticClass:\"slide-container\",style:(_vm.slideContainerStyle)},_vm._l((_vm.pages),function(page,index){return _c('div',{key:index,staticClass:\"slide-page\",class:{ 'slide-page-active': index + 1 === _vm.currentPage }},[(page)?_c('img',{staticClass:\"slide-page-image\",attrs:{\"src\":page,\"alt\":\"\"}}):_c('div',{staticClass:\"slide-page-placeholder\"},[_vm._v(\"封面/封底\")])])}),0)]):(_vm.flipMode === 'fade')?_c('div',{ref:\"fadeViewer\",class:['fade-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']},[_c('transition',{attrs:{\"name\":\"fade-page\",\"mode\":\"out-in\"}},[_c('div',{key:_vm.currentPage,staticClass:\"fade-page-container\"},[(_vm.pages[_vm.currentPage - 1])?_c('img',{staticClass:\"fade-page-image\",attrs:{\"src\":_vm.pages[_vm.currentPage - 1],\"alt\":\"\"}}):_c('div',{staticClass:\"fade-page-placeholder\"},[_vm._v(\"封面/封底\")])])])],1):(_vm.flipMode === 'scroll')?_c('div',{ref:\"scrollViewer\",class:['scroll-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized'],on:{\"scroll\":_vm.onScrollViewerScroll}},[_c('div',{staticClass:\"scroll-container\"},_vm._l((_vm.pages),function(page,index){return _c('div',{key:index,ref:'scrollPage' + index,refInFor:true,staticClass:\"scroll-page\"},[(page)?_c('img',{staticClass:\"scroll-page-image\",attrs:{\"src\":page,\"alt\":\"\"}}):_c('div',{staticClass:\"scroll-page-placeholder\"},[_vm._v(\"封面/封底\")])])}),0)]):(_vm.flipMode === 'clip')?_c('div',{ref:\"clipViewer\",class:['clip-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']},[_c('div',{staticClass:\"clip-pages-wrapper\"},[_c('transition',{attrs:{\"name\":\"clip-current\"}},[_c('div',{key:'current-' + _vm.currentPage,staticClass:\"clip-page clip-page-current\"},[(_vm.pages[_vm.currentPage - 1])?_c('img',{staticClass:\"clip-page-image\",attrs:{\"src\":_vm.pages[_vm.currentPage - 1],\"alt\":\"\"}}):_c('div',{staticClass:\"clip-page-placeholder\"},[_vm._v(\"封面/封底\")])])]),(_vm.currentPage < _vm.totalPages)?_c('div',{staticClass:\"clip-page clip-page-next\"},[(_vm.pages[_vm.currentPage])?_c('img',{staticClass:\"clip-page-image\",attrs:{\"src\":_vm.pages[_vm.currentPage],\"alt\":\"\"}}):_c('div',{staticClass:\"clip-page-placeholder\"},[_vm._v(\"封面/封底\")])]):_vm._e()],1)]):(_vm.flipMode === 'card')?_c('div',{ref:\"cardViewer\",class:['card-viewer', _vm.isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']},[_c('div',{staticClass:\"card-stack\"},[_c('transition-group',{staticClass:\"card-transition-group\",attrs:{\"name\":\"card-flip\",\"tag\":\"div\"}},_vm._l((_vm.visibleCards),function(page,index){return _c('div',{key:'card-' + page.originalIndex,staticClass:\"card-item\",class:{\n 'card-item-prev': page.position === 'prev',\n 'card-item-current': page.position === 'current',\n 'card-item-next': page.position === 'next'\n },style:(_vm.getCardStyle(page.position))},[_c('div',{staticClass:\"card-content\"},[(page.src)?_c('img',{staticClass:\"card-image\",attrs:{\"src\":page.src,\"alt\":\"\"}}):_c('div',{staticClass:\"card-placeholder\"},[_vm._v(\"封面/封底\")])]),_c('div',{staticClass:\"card-page-number\"},[_vm._v(_vm._s(page.originalIndex + 1)+\" / \"+_vm._s(_vm.totalPages))])])}),0)],1)]):_vm._e()],1):_vm._e(),_c('div',{staticClass:\"controls\",class:{ 'mobile-controls': _vm.isMobile, 'desktop-controls': !_vm.isMobile, 'controls-visible': _vm.showControls },on:{\"click\":function($event){$event.stopPropagation();}}},[_c('button',{staticClass:\"btn btn-prev\",on:{\"click\":_vm.flipLeft}},[_vm._v(\" ← 上一页 \")]),_c('span',{staticClass:\"page-indicator\"},[(_vm.totalPages === 0)?_c('span',[_vm._v(\"加载中...\")]):(_vm.currentPage === 1 && _vm.totalPages > 1)?_c('span',[_vm._v(\"封面\")]):(_vm.currentPage === _vm.totalPages && _vm.totalPages > 1)?_c('span',[_vm._v(\"封底\")]):(!_vm.isMobile && _vm.totalPages > 2)?_c('span',[_vm._v(\"第 \"+_vm._s(Math.ceil((_vm.currentPage - 1) / 2))+\" 组 / 共 \"+_vm._s(Math.ceil((_vm.totalPages - 2) / 2))+\" 组\")]):(_vm.isMobile && _vm.totalPages > 2)?_c('span',[_vm._v(\"第 \"+_vm._s(Math.max(1, _vm.currentPage ))+\" 页 / 共 \"+_vm._s(Math.max(0, _vm.totalPages - 2))+\" 页\")]):(_vm.totalPages === 1)?_c('span',[_vm._v(\"第 1 页 / 共 1 页\")]):_c('span',[_vm._v(\"第 \"+_vm._s(_vm.currentPage)+\" 页 / 共 \"+_vm._s(_vm.totalPages)+\" 页\")])]),_c('button',{staticClass:\"btn btn-next\",on:{\"click\":_vm.flipRight}},[_vm._v(\" 下一页 → \")])]),(_vm.showFlipModeSelector)?_c('div',{staticClass:\"flip-mode-selector\",class:{ 'mobile-flip-mode-selector': _vm.isMobile }},[_c('button',{staticClass:\"btn btn-flip-mode\",on:{\"click\":_vm.toggleFlipModeMenu}},[_c('span',{staticClass:\"flip-mode-icon\"},[_vm._v(_vm._s(_vm.flipModeIcon))]),_c('span',{staticClass:\"flip-mode-text\"},[_vm._v(_vm._s(_vm.flipModeLabel))])]),(_vm.showFlipModeMenu)?_c('div',{staticClass:\"flip-mode-menu\"},_vm._l((_vm.flipModes),function(mode){return _c('div',{key:mode.value,staticClass:\"flip-mode-item\",class:{ 'flip-mode-item-active': _vm.flipMode === mode.value },on:{\"click\":function($event){return _vm.selectFlipMode(mode.value)}}},[_c('span',{staticClass:\"flip-mode-item-icon\"},[_vm._v(_vm._s(mode.icon))]),_c('span',{staticClass:\"flip-mode-item-label\"},[_vm._v(_vm._s(mode.label))])])}),0):_vm._e()]):_vm._e(),_c('div',{staticClass:\"catalogue-button\",class:{\n 'mobile-catalogue-button': _vm.isMobile,\n 'dragging': _vm.catalogueButtonDragging,\n 'catalogue-visible': _vm.showControls\n },style:({\n left: _vm.catalogueButtonPosition.x + 'px',\n top: _vm.catalogueButtonPosition.y + 'px'\n }),on:{\"mousedown\":_vm.onCatalogueMouseDown,\"touchstart\":_vm.onCatalogueTouchStart,\"touchmove\":_vm.onCatalogueTouchMove,\"touchend\":_vm.onCatalogueTouchEnd,\"click\":function($event){$event.stopPropagation();}}},[_c('button',{staticClass:\"btn btn-catalogue\",on:{\"click\":_vm.openCatalogue}},[_c('span',{staticClass:\"catalogue-text\"},[_vm._v(\"目录\")])])]),_c('div',{staticClass:\"zoom-hint\",class:{ 'show': _vm.zoomScale !== 1 }},[_c('div',{staticClass:\"zoom-info\"},[_c('span',[_vm._v(\"缩放: \"+_vm._s(Math.round(_vm.zoomScale * 100))+\"%\")]),_c('button',{staticClass:\"btn-reset-zoom\",on:{\"click\":_vm.resetZoom}},[_vm._v(\"重置\")])])]),_c('div',{staticClass:\"zoom-toolbar\"},[_c('button',{staticClass:\"btn-zoom\",attrs:{\"disabled\":_vm.zoomScale <= 0.5},on:{\"click\":_vm.zoomOut}},[_vm._v(\"-\")]),_c('button',{staticClass:\"btn-zoom\",attrs:{\"disabled\":_vm.zoomScale >= 3},on:{\"click\":_vm.zoomIn}},[_vm._v(\"+\")])]),(_vm.loading)?_c('div',{staticClass:\"loading-overlay\"},[_vm._m(0)]):_vm._e(),(_vm.error && !_vm.loading)?_c('div',{staticClass:\"error-container\"},[_c('div',{staticClass:\"error-content\"},[_c('div',{staticClass:\"error-icon\"},[_vm._v(\"⚠️\")]),_c('h3',[_vm._v(\"加载失败\")]),_c('p',[_vm._v(_vm._s(_vm.error.message || '网络连接异常,请检查网络后重试'))]),_c('button',{staticClass:\"retry-btn\",on:{\"click\":_vm.fetchBooksData}},[_c('span',{staticClass:\"retry-icon\"},[_vm._v(\"🔄\")]),_vm._v(\" 重新加载 \")])])]):_vm._e(),(_vm.booksData && !_vm.loading && !_vm.error)?_c('div',{staticClass:\"success-toast\",class:{ 'show': _vm.showSuccessToast }},[_vm._v(\" 书籍加载完成 \")]):_vm._e(),_c('book-catalogue-drawer',{attrs:{\"book-id\":_vm.bookId,\"catalogue\":_vm.catalogue,\"loading\":_vm.catalogueLoading},on:{\"catalogue-click\":_vm.onCatalogueClick,\"page-jump\":_vm.onPageJump,\"Focus\":_vm.onFocus,\"fetch-catalogue\":_vm.onFetchCatalogue},model:{value:(_vm.showCatalogueDrawer),callback:function ($$v) {_vm.showCatalogueDrawer=$$v},expression:\"showCatalogueDrawer\"}})],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"loading-container\"},[_c('div',{staticClass:\"loading-spinner\"},[_c('div',{staticClass:\"spinner-ring\"}),_c('div',{staticClass:\"spinner-ring\"}),_c('div',{staticClass:\"spinner-ring\"})]),_c('div',{staticClass:\"loading-text\"},[_c('h3',[_vm._v(\"正在加载书籍\")]),_c('p',[_vm._v(\"请稍候,正在获取精彩内容...\")]),_c('div',{staticClass:\"loading-progress\"},[_c('div',{staticClass:\"progress-bar\"})])])])\n}]\n\nexport { render, staticRenderFns }","export * from \"-!../../node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[2]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=template&id=4702ca70&scoped=true\"","/*! @license Rematrix v0.7.2\n\n\tCopyright 2021 Julian Lloyd.\n\n\tPermission is hereby granted, free of charge, to any person obtaining a copy\n\tof this software and associated documentation files (the \"Software\"), to deal\n\tin the Software without restriction, including without limitation the rights\n\tto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n\tcopies of the Software, and to permit persons to whom the Software is\n\tfurnished to do so, subject to the following conditions:\n\n\tThe above copyright notice and this permission notice shall be included in\n\tall copies or substantial portions of the Software.\n\n\tTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n\tIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n\tFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n\tAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n\tLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n\tOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n\tTHE SOFTWARE.\n*/\nfunction format(source) {\n if (source && source.constructor === Array) {\n var values = source\n .filter(function (value) { return typeof value === 'number'; })\n .filter(function (value) { return !isNaN(value); });\n\n if (source.length === 6 && values.length === 6) {\n var matrix = identity();\n matrix[0] = values[0];\n matrix[1] = values[1];\n matrix[4] = values[2];\n matrix[5] = values[3];\n matrix[12] = values[4];\n matrix[13] = values[5];\n return matrix\n } else if (source.length === 16 && values.length === 16) {\n return source\n }\n }\n throw new TypeError('Expected a `number[]` with length 6 or 16.')\n}\n\nfunction fromString(source) {\n if (typeof source === 'string') {\n var match = source.match(/matrix(3d)?\\(([^)]+)\\)/);\n if (match) {\n var raw = match[2].split(',').map(parseFloat);\n return format(raw)\n }\n if (source === 'none' || source === '') {\n return identity()\n }\n }\n throw new TypeError('Expected a string containing `matrix()` or `matrix3d()')\n}\n\nfunction identity() {\n var matrix = [];\n for (var i = 0; i < 16; i++) {\n i % 5 == 0 ? matrix.push(1) : matrix.push(0);\n }\n return matrix\n}\n\nfunction inverse(source) {\n var m = format(source);\n\n var s0 = m[0] * m[5] - m[4] * m[1];\n var s1 = m[0] * m[6] - m[4] * m[2];\n var s2 = m[0] * m[7] - m[4] * m[3];\n var s3 = m[1] * m[6] - m[5] * m[2];\n var s4 = m[1] * m[7] - m[5] * m[3];\n var s5 = m[2] * m[7] - m[6] * m[3];\n\n var c5 = m[10] * m[15] - m[14] * m[11];\n var c4 = m[9] * m[15] - m[13] * m[11];\n var c3 = m[9] * m[14] - m[13] * m[10];\n var c2 = m[8] * m[15] - m[12] * m[11];\n var c1 = m[8] * m[14] - m[12] * m[10];\n var c0 = m[8] * m[13] - m[12] * m[9];\n\n var determinant = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0);\n\n if (isNaN(determinant) || determinant === Infinity) {\n throw new Error('Inverse determinant attempted to divide by zero.')\n }\n\n return [\n (m[5] * c5 - m[6] * c4 + m[7] * c3) * determinant,\n (-m[1] * c5 + m[2] * c4 - m[3] * c3) * determinant,\n (m[13] * s5 - m[14] * s4 + m[15] * s3) * determinant,\n (-m[9] * s5 + m[10] * s4 - m[11] * s3) * determinant,\n\n (-m[4] * c5 + m[6] * c2 - m[7] * c1) * determinant,\n (m[0] * c5 - m[2] * c2 + m[3] * c1) * determinant,\n (-m[12] * s5 + m[14] * s2 - m[15] * s1) * determinant,\n (m[8] * s5 - m[10] * s2 + m[11] * s1) * determinant,\n\n (m[4] * c4 - m[5] * c2 + m[7] * c0) * determinant,\n (-m[0] * c4 + m[1] * c2 - m[3] * c0) * determinant,\n (m[12] * s4 - m[13] * s2 + m[15] * s0) * determinant,\n (-m[8] * s4 + m[9] * s2 - m[11] * s0) * determinant,\n\n (-m[4] * c3 + m[5] * c1 - m[6] * c0) * determinant,\n (m[0] * c3 - m[1] * c1 + m[2] * c0) * determinant,\n (-m[12] * s3 + m[13] * s1 - m[14] * s0) * determinant,\n (m[8] * s3 - m[9] * s1 + m[10] * s0) * determinant ]\n}\n\nfunction multiply(matrixA, matrixB) {\n var fma = format(matrixA);\n var fmb = format(matrixB);\n var product = [];\n\n for (var i = 0; i < 4; i++) {\n var row = [fma[i], fma[i + 4], fma[i + 8], fma[i + 12]];\n for (var j = 0; j < 4; j++) {\n var k = j * 4;\n var col = [fmb[k], fmb[k + 1], fmb[k + 2], fmb[k + 3]];\n var result = row[0] * col[0] + row[1] * col[1] + row[2] * col[2] + row[3] * col[3];\n\n product[i + k] = result;\n }\n }\n\n return product\n}\n\nfunction perspective(distance) {\n var matrix = identity();\n matrix[11] = -1 / distance;\n return matrix\n}\n\nfunction rotate(angle) {\n return rotateZ(angle)\n}\n\nfunction rotateX(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[5] = matrix[10] = Math.cos(theta);\n matrix[6] = matrix[9] = Math.sin(theta);\n matrix[9] *= -1;\n\n return matrix\n}\n\nfunction rotateY(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[0] = matrix[10] = Math.cos(theta);\n matrix[2] = matrix[8] = Math.sin(theta);\n matrix[2] *= -1;\n\n return matrix\n}\n\nfunction rotateZ(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[0] = matrix[5] = Math.cos(theta);\n matrix[1] = matrix[4] = Math.sin(theta);\n matrix[4] *= -1;\n\n return matrix\n}\n\nfunction scale(scalar, scalarY) {\n var matrix = identity();\n\n matrix[0] = scalar;\n matrix[5] = typeof scalarY === 'number' ? scalarY : scalar;\n\n return matrix\n}\n\nfunction scaleX(scalar) {\n var matrix = identity();\n matrix[0] = scalar;\n return matrix\n}\n\nfunction scaleY(scalar) {\n var matrix = identity();\n matrix[5] = scalar;\n return matrix\n}\n\nfunction scaleZ(scalar) {\n var matrix = identity();\n matrix[10] = scalar;\n return matrix\n}\n\nfunction skew(angleX, angleY) {\n var thetaX = (Math.PI / 180) * angleX;\n var matrix = identity();\n\n matrix[4] = Math.tan(thetaX);\n\n if (angleY) {\n var thetaY = (Math.PI / 180) * angleY;\n matrix[1] = Math.tan(thetaY);\n }\n\n return matrix\n}\n\nfunction skewX(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[4] = Math.tan(theta);\n\n return matrix\n}\n\nfunction skewY(angle) {\n var theta = (Math.PI / 180) * angle;\n var matrix = identity();\n\n matrix[1] = Math.tan(theta);\n\n return matrix\n}\n\nfunction toString(source) {\n return (\"matrix3d(\" + (format(source).join(', ')) + \")\")\n}\n\nfunction translate(distanceX, distanceY) {\n var matrix = identity();\n matrix[12] = distanceX;\n\n if (distanceY) {\n matrix[13] = distanceY;\n }\n\n return matrix\n}\n\nfunction translate3d(distanceX, distanceY, distanceZ) {\n var matrix = identity();\n if (distanceX !== undefined && distanceY !== undefined && distanceZ !== undefined) {\n matrix[12] = distanceX;\n matrix[13] = distanceY;\n matrix[14] = distanceZ;\n }\n return matrix\n}\n\nfunction translateX(distance) {\n var matrix = identity();\n matrix[12] = distance;\n return matrix\n}\n\nfunction translateY(distance) {\n var matrix = identity();\n matrix[13] = distance;\n return matrix\n}\n\nfunction translateZ(distance) {\n var matrix = identity();\n matrix[14] = distance;\n return matrix\n}\n\nexport { format, fromString, identity, inverse, multiply, perspective, rotate, rotateX, rotateY, rotateZ, scale, scaleX, scaleY, scaleZ, skew, skewX, skewY, toString, translate, translate3d, translateX, translateY, translateZ };\n","/*!\n * @license\n * flipbook-vue v1.0.0-beta.4\n * Copyright © 2023 Takeshi Sone.\n * Released under the MIT License.\n */\n\nimport { multiply, perspective, translate, translate3d, rotateY, toString, identity } from 'rematrix';\n\nvar Matrix = /*@__PURE__*/(function () {\n function Matrix(arg) {\n if (arg) {\n if (arg.m) {\n this.m = [].concat( arg.m );\n } else {\n this.m = [].concat( arg );\n }\n } else {\n this.m = identity();\n }\n }\n\n Matrix.prototype.clone = function clone () {\n return new Matrix(this);\n };\n\n Matrix.prototype.multiply = function multiply$1 (m) {\n return this.m = multiply(this.m, m);\n };\n\n Matrix.prototype.perspective = function perspective$1 (d) {\n return this.multiply(perspective(d));\n };\n\n Matrix.prototype.transformX = function transformX (x) {\n return (x * this.m[0] + this.m[12]) / (x * this.m[3] + this.m[15]);\n };\n\n Matrix.prototype.translate = function translate$1 (x, y) {\n return this.multiply(translate(x, y));\n };\n\n Matrix.prototype.translate3d = function translate3d$1 (x, y, z) {\n return this.multiply(translate3d(x, y, z));\n };\n\n Matrix.prototype.rotateY = function rotateY$1 (deg) {\n return this.multiply(rotateY(deg));\n };\n\n Matrix.prototype.toString = function toString$1 () {\n return toString(this.m);\n };\n\n return Matrix;\n}());\n\nvar spinner = \"data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22500%22%20height%3D%22500%22%20viewBox%3D%220%200%20500%20500%22%20fill%3D%22transparent%22%20style%3D%22background-color%3A%20%23fff%22%3E%20%20%3Ccircle%20%20%20%20cx%3D%22250%22%20%20%20%20cy%3D%22250%22%20%20%20%20r%3D%2248%22%20%20%20%20stroke%3D%22%23333%22%20%20%20%20stroke-width%3D%222%22%20%20%20%20stroke-dasharray%3D%22271%2030%22%20%20%3E%20%20%20%20%3CanimateTransform%20%20%20%20%20%20attributeName%3D%22transform%22%20%20%20%20%20%20attributeType%3D%22XML%22%20%20%20%20%20%20type%3D%22rotate%22%20%20%20%20%20%20from%3D%220%20250%20250%22%20%20%20%20%20%20to%3D%22360%20250%20250%22%20%20%20%20%20%20dur%3D%221s%22%20%20%20%20%20%20repeatCount%3D%22indefinite%22%20%20%20%20%2F%3E%20%20%3C%2Fcircle%3E%3C%2Fsvg%3E\";\n\nvar easeIn, easeInOut, easeOut;\n\neaseIn = function(x) {\n return Math.pow(x, 2);\n};\n\neaseOut = function(x) {\n return 1 - easeIn(1 - x);\n};\n\neaseInOut = function(x) {\n if (x < 0.5) {\n return easeIn(x * 2) / 2;\n } else {\n return 0.5 + easeOut((x - 0.5) * 2) / 2;\n }\n};\n\nvar script = {\n props: {\n pages: {\n type: Array,\n required: true\n },\n pagesHiRes: {\n type: Array,\n default: function() {\n return [];\n }\n },\n flipDuration: {\n type: Number,\n default: 1000\n },\n zoomDuration: {\n type: Number,\n default: 500\n },\n zooms: {\n type: Array,\n default: function() {\n return [1, 2, 4];\n }\n },\n perspective: {\n type: Number,\n default: 2400\n },\n nPolygons: {\n type: Number,\n default: 10\n },\n ambient: {\n type: Number,\n default: 0.4\n },\n gloss: {\n type: Number,\n default: 0.6\n },\n swipeMin: {\n type: Number,\n default: 3\n },\n singlePage: {\n type: Boolean,\n default: false\n },\n forwardDirection: {\n validator: function(val) {\n return val === 'right' || val === 'left';\n },\n default: 'right'\n },\n centering: {\n type: Boolean,\n default: true\n },\n startPage: {\n type: Number,\n default: null\n },\n loadingImage: {\n type: String,\n default: spinner\n },\n clickToZoom: {\n type: Boolean,\n default: true\n },\n dragToFlip: {\n type: Boolean,\n default: true\n },\n wheel: {\n type: String,\n default: 'scroll'\n }\n },\n data: function() {\n return {\n viewWidth: 0,\n viewHeight: 0,\n imageWidth: null,\n imageHeight: null,\n displayedPages: 1,\n nImageLoad: 0,\n nImageLoadTrigger: 0,\n imageLoadCallback: null,\n currentPage: 0,\n firstPage: 0,\n secondPage: 1,\n zoomIndex: 0,\n zoom: 1,\n zooming: false,\n touchStartX: null,\n touchStartY: null,\n maxMove: 0,\n activeCursor: null,\n hasTouchEvents: false,\n hasPointerEvents: false,\n minX: 2e308,\n maxX: -2e308,\n preloadedImages: {},\n flip: {\n progress: 0,\n direction: null,\n frontImage: null,\n backImage: null,\n auto: false,\n opacity: 1\n },\n currentCenterOffset: null,\n animatingCenter: false,\n startScrollLeft: 0,\n startScrollTop: 0,\n scrollLeft: 0,\n scrollTop: 0,\n loadedImages: {}\n };\n },\n computed: {\n IE: function() {\n return typeof navigator !== 'undefined' && /Trident/.test(navigator.userAgent);\n },\n canFlipLeft: function() {\n if (this.forwardDirection === 'left') {\n return this.canGoForward;\n } else {\n return this.canGoBack;\n }\n },\n canFlipRight: function() {\n if (this.forwardDirection === 'right') {\n return this.canGoForward;\n } else {\n return this.canGoBack;\n }\n },\n canZoomIn: function() {\n return !this.zooming && this.zoomIndex < this.zooms_.length - 1;\n },\n canZoomOut: function() {\n return !this.zooming && this.zoomIndex > 0;\n },\n numPages: function() {\n if (this.pages[0] === null) {\n return this.pages.length - 1;\n } else {\n return this.pages.length;\n }\n },\n page: function() {\n if (this.pages[0] !== null) {\n return this.currentPage + 1;\n } else {\n return Math.max(1, this.currentPage);\n }\n },\n zooms_: function() {\n return this.zooms || [1];\n },\n canGoForward: function() {\n return !this.flip.direction && this.currentPage < this.pages.length - this.displayedPages;\n },\n canGoBack: function() {\n return !this.flip.direction && this.currentPage >= this.displayedPages && !(this.displayedPages === 1 && !this.pageUrl(this.firstPage - 1));\n },\n leftPage: function() {\n if (this.forwardDirection === 'right' || this.displayedPages === 1) {\n return this.firstPage;\n } else {\n return this.secondPage;\n }\n },\n rightPage: function() {\n if (this.forwardDirection === 'left') {\n return this.firstPage;\n } else {\n return this.secondPage;\n }\n },\n showLeftPage: function() {\n return this.pageUrl(this.leftPage);\n },\n showRightPage: function() {\n return this.pageUrl(this.rightPage) && this.displayedPages === 2;\n },\n cursor: function() {\n if (this.activeCursor) {\n return this.activeCursor;\n } else if (this.IE) {\n return 'auto';\n } else if (this.clickToZoom && this.canZoomIn) {\n return 'zoom-in';\n } else if (this.clickToZoom && this.canZoomOut) {\n return 'zoom-out';\n } else if (this.dragToFlip) {\n return 'grab';\n } else {\n return 'auto';\n }\n },\n pageScale: function() {\n var scale, vw, xScale, yScale;\n vw = this.viewWidth / this.displayedPages;\n xScale = vw / this.imageWidth;\n yScale = this.viewHeight / this.imageHeight;\n scale = xScale < yScale ? xScale : yScale;\n if (scale < 1) {\n return scale;\n } else {\n return 1;\n }\n },\n pageWidth: function() {\n return Math.round(this.imageWidth * this.pageScale);\n },\n pageHeight: function() {\n return Math.round(this.imageHeight * this.pageScale);\n },\n xMargin: function() {\n return (this.viewWidth - this.pageWidth * this.displayedPages) / 2;\n },\n yMargin: function() {\n return (this.viewHeight - this.pageHeight) / 2;\n },\n polygonWidth: function() {\n var w;\n w = this.pageWidth / this.nPolygons;\n w = Math.ceil(w + 1 / this.zoom);\n return w + 'px';\n },\n polygonHeight: function() {\n return this.pageHeight + 'px';\n },\n polygonBgSize: function() {\n return ((this.pageWidth) + \"px \" + (this.pageHeight) + \"px\");\n },\n polygonArray: function() {\n return this.makePolygonArray('front').concat(this.makePolygonArray('back'));\n },\n boundingLeft: function() {\n var x;\n if (this.displayedPages === 1) {\n return this.xMargin;\n } else {\n x = this.pageUrl(this.leftPage) ? this.xMargin : this.viewWidth / 2;\n if (x < this.minX) {\n return x;\n } else {\n return this.minX;\n }\n }\n },\n boundingRight: function() {\n var x;\n if (this.displayedPages === 1) {\n return this.viewWidth - this.xMargin;\n } else {\n x = this.pageUrl(this.rightPage) ? this.viewWidth - this.xMargin : this.viewWidth / 2;\n if (x > this.maxX) {\n return x;\n } else {\n return this.maxX;\n }\n }\n },\n centerOffset: function() {\n var retval;\n retval = this.centering ? Math.round(this.viewWidth / 2 - (this.boundingLeft + this.boundingRight) / 2) : 0;\n if (this.currentCenterOffset === null && this.imageWidth !== null) {\n this.currentCenterOffset = retval;\n }\n return retval;\n },\n centerOffsetSmoothed: function() {\n return Math.round(this.currentCenterOffset);\n },\n dragToScroll: function() {\n return !this.hasTouchEvents;\n },\n scrollLeftMin: function() {\n var w;\n w = (this.boundingRight - this.boundingLeft) * this.zoom;\n if (w < this.viewWidth) {\n return (this.boundingLeft + this.centerOffsetSmoothed) * this.zoom - (this.viewWidth - w) / 2;\n } else {\n return (this.boundingLeft + this.centerOffsetSmoothed) * this.zoom;\n }\n },\n scrollLeftMax: function() {\n var w;\n w = (this.boundingRight - this.boundingLeft) * this.zoom;\n if (w < this.viewWidth) {\n return (this.boundingLeft + this.centerOffsetSmoothed) * this.zoom - (this.viewWidth - w) / 2;\n } else {\n return (this.boundingRight + this.centerOffsetSmoothed) * this.zoom - this.viewWidth;\n }\n },\n scrollTopMin: function() {\n var h;\n h = this.pageHeight * this.zoom;\n if (h < this.viewHeight) {\n return this.yMargin * this.zoom - (this.viewHeight - h) / 2;\n } else {\n return this.yMargin * this.zoom;\n }\n },\n scrollTopMax: function() {\n var h;\n h = this.pageHeight * this.zoom;\n if (h < this.viewHeight) {\n return this.yMargin * this.zoom - (this.viewHeight - h) / 2;\n } else {\n return (this.yMargin + this.pageHeight) * this.zoom - this.viewHeight;\n }\n },\n scrollLeftLimited: function() {\n return Math.min(this.scrollLeftMax, Math.max(this.scrollLeftMin, this.scrollLeft));\n },\n scrollTopLimited: function() {\n return Math.min(this.scrollTopMax, Math.max(this.scrollTopMin, this.scrollTop));\n }\n },\n mounted: function() {\n window.addEventListener('resize', this.onResize, {\n passive: true\n });\n this.onResize();\n this.zoom = this.zooms_[0];\n return this.goToPage(this.startPage);\n },\n beforeDestroy: function() {\n return window.removeEventListener('resize', this.onResize, {\n passive: true\n });\n },\n methods: {\n onResize: function() {\n var viewport;\n viewport = this.$refs.viewport;\n if (!viewport) {\n return;\n }\n this.viewWidth = viewport.clientWidth;\n this.viewHeight = viewport.clientHeight;\n this.displayedPages = this.viewWidth > this.viewHeight && !this.singlePage ? 2 : 1;\n if (this.displayedPages === 2) {\n this.currentPage &= ~1;\n }\n this.fixFirstPage();\n this.minX = 2e308;\n return this.maxX = -2e308;\n },\n fixFirstPage: function() {\n if (this.displayedPages === 1 && this.currentPage === 0 && this.pages.length && !this.pageUrl(0)) {\n return this.currentPage++;\n }\n },\n pageUrl: function(page, hiRes) {\n if ( hiRes === void 0 ) hiRes = false;\n\n var url;\n if (hiRes && this.zoom > 1 && !this.zooming) {\n url = this.pagesHiRes[page];\n if (url) {\n return url;\n }\n }\n return this.pages[page] || null;\n },\n pageUrlLoading: function(page, hiRes) {\n if ( hiRes === void 0 ) hiRes = false;\n\n var url;\n url = this.pageUrl(page, hiRes);\n if (hiRes && this.zoom > 1 && !this.zooming) {\n // High-res image doesn't use 'loading'\n return url;\n }\n return url && this.loadImage(url);\n },\n flipLeft: function() {\n if (!this.canFlipLeft) {\n return;\n }\n return this.flipStart('left', true);\n },\n flipRight: function() {\n if (!this.canFlipRight) {\n return;\n }\n return this.flipStart('right', true);\n },\n makePolygonArray: function(face) {\n var bgPos, dRadian, dRotate, direction, i, image, j, lighting, m, originRight, pageMatrix, pageRotation, pageX, polygonWidth, progress, rad, radian, radius, ref, results, rotate, theta, x, x0, x1, z;\n if (!this.flip.direction) {\n return [];\n }\n progress = this.flip.progress;\n direction = this.flip.direction;\n if (this.displayedPages === 1 && direction !== this.forwardDirection) {\n progress = 1 - progress;\n direction = this.forwardDirection;\n }\n this.flip.opacity = this.displayedPages === 1 && progress > .7 ? 1 - (progress - .7) / .3 : 1;\n image = face === 'front' ? this.flip.frontImage : this.flip.backImage;\n polygonWidth = this.pageWidth / this.nPolygons;\n pageX = this.xMargin;\n originRight = false;\n if (this.displayedPages === 1) {\n if (this.forwardDirection === 'right') {\n if (face === 'back') {\n originRight = true;\n pageX = this.xMargin - this.pageWidth;\n }\n } else {\n if (direction === 'left') {\n if (face === 'back') {\n pageX = this.pageWidth - this.xMargin;\n } else {\n originRight = true;\n }\n } else {\n if (face === 'front') {\n pageX = this.pageWidth - this.xMargin;\n } else {\n originRight = true;\n }\n }\n }\n } else {\n if (direction === 'left') {\n if (face === 'back') {\n pageX = this.viewWidth / 2;\n } else {\n originRight = true;\n }\n } else {\n if (face === 'front') {\n pageX = this.viewWidth / 2;\n } else {\n originRight = true;\n }\n }\n }\n pageMatrix = new Matrix();\n pageMatrix.translate(this.viewWidth / 2);\n pageMatrix.perspective(this.perspective);\n pageMatrix.translate(-this.viewWidth / 2);\n pageMatrix.translate(pageX, this.yMargin);\n pageRotation = 0;\n if (progress > 0.5) {\n pageRotation = -(progress - 0.5) * 2 * 180;\n }\n if (direction === 'left') {\n pageRotation = -pageRotation;\n }\n if (face === 'back') {\n pageRotation += 180;\n }\n if (pageRotation) {\n if (originRight) {\n pageMatrix.translate(this.pageWidth);\n }\n pageMatrix.rotateY(pageRotation);\n if (originRight) {\n pageMatrix.translate(-this.pageWidth);\n }\n }\n if (progress < 0.5) {\n theta = progress * 2 * Math.PI;\n } else {\n theta = (1 - (progress - 0.5) * 2) * Math.PI;\n }\n if (theta === 0) {\n theta = 1e-9;\n }\n radius = this.pageWidth / theta;\n radian = 0;\n dRadian = theta / this.nPolygons;\n rotate = dRadian / 2 / Math.PI * 180;\n dRotate = dRadian / Math.PI * 180;\n if (originRight) {\n rotate = -theta / Math.PI * 180 + dRotate / 2;\n }\n if (face === 'back') {\n rotate = -rotate;\n dRotate = -dRotate;\n }\n this.minX = 2e308;\n this.maxX = -2e308;\n results = [];\n for (i = j = 0, ref = this.nPolygons; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {\n bgPos = (i / (this.nPolygons - 1) * 100) + \"% 0px\";\n m = pageMatrix.clone();\n rad = originRight ? theta - radian : radian;\n x = Math.sin(rad) * radius;\n if (originRight) {\n x = this.pageWidth - x;\n }\n z = (1 - Math.cos(rad)) * radius;\n if (face === 'back') {\n z = -z;\n }\n m.translate3d(x, 0, z);\n m.rotateY(-rotate);\n x0 = m.transformX(0);\n x1 = m.transformX(polygonWidth);\n this.maxX = Math.max(Math.max(x0, x1), this.maxX);\n this.minX = Math.min(Math.min(x0, x1), this.minX);\n lighting = this.computeLighting(pageRotation - rotate, dRotate);\n radian += dRadian;\n rotate += dRotate;\n results.push([face + i, image, lighting, bgPos, m.toString(), Math.abs(Math.round(z))]);\n }\n return results;\n },\n computeLighting: function(rot, dRotate) {\n var DEG, POW, blackness, diffuse, gradients, lightingPoints, specular;\n gradients = [];\n lightingPoints = [-0.5, -0.25, 0, 0.25, 0.5];\n if (this.ambient < 1) {\n blackness = 1 - this.ambient;\n diffuse = lightingPoints.map(function (d) {\n return (1 - Math.cos((rot - dRotate * d) / 180 * Math.PI)) * blackness;\n });\n gradients.push((\"linear-gradient(to right,\\n rgba(0, 0, 0, \" + (diffuse[0]) + \"),\\n rgba(0, 0, 0, \" + (diffuse[1]) + \") 25%,\\n rgba(0, 0, 0, \" + (diffuse[2]) + \") 50%,\\n rgba(0, 0, 0, \" + (diffuse[3]) + \") 75%,\\n rgba(0, 0, 0, \" + (diffuse[4]) + \"))\"));\n }\n if (this.gloss > 0 && !this.IE) {\n DEG = 30;\n POW = 200;\n specular = lightingPoints.map(function (d) {\n return Math.max(Math.pow( Math.cos((rot + DEG - dRotate * d) / 180 * Math.PI), POW ), Math.pow( Math.cos((rot - DEG - dRotate * d) / 180 * Math.PI), POW ));\n });\n gradients.push((\"linear-gradient(to right,\\n rgba(255, 255, 255, \" + (specular[0] * this.gloss) + \"),\\n rgba(255, 255, 255, \" + (specular[1] * this.gloss) + \") 25%,\\n rgba(255, 255, 255, \" + (specular[2] * this.gloss) + \") 50%,\\n rgba(255, 255, 255, \" + (specular[3] * this.gloss) + \") 75%,\\n rgba(255, 255, 255, \" + (specular[4] * this.gloss) + \"))\"));\n }\n return gradients.join(',');\n },\n flipStart: function(direction, auto) {\n var this$1$1 = this;\n\n if (direction !== this.forwardDirection) {\n if (this.displayedPages === 1) {\n this.flip.frontImage = this.pageUrl(this.currentPage - 1);\n this.flip.backImage = null;\n } else {\n this.flip.frontImage = this.pageUrl(this.firstPage);\n this.flip.backImage = this.pageUrl(this.currentPage - this.displayedPages + 1);\n }\n } else {\n if (this.displayedPages === 1) {\n this.flip.frontImage = this.pageUrl(this.currentPage);\n this.flip.backImage = null;\n } else {\n this.flip.frontImage = this.pageUrl(this.secondPage);\n this.flip.backImage = this.pageUrl(this.currentPage + this.displayedPages);\n }\n }\n this.flip.direction = direction;\n this.flip.progress = 0;\n return requestAnimationFrame(function () {\n return requestAnimationFrame(function () {\n if (this$1$1.flip.direction !== this$1$1.forwardDirection) {\n if (this$1$1.displayedPages === 2) {\n this$1$1.firstPage = this$1$1.currentPage - this$1$1.displayedPages;\n }\n } else {\n if (this$1$1.displayedPages === 1) {\n this$1$1.firstPage = this$1$1.currentPage + this$1$1.displayedPages;\n } else {\n this$1$1.secondPage = this$1$1.currentPage + 1 + this$1$1.displayedPages;\n }\n }\n if (auto) {\n return this$1$1.flipAuto(true);\n }\n });\n });\n },\n flipAuto: function(ease) {\n var this$1$1 = this;\n\n var animate, duration, startRatio, t0;\n t0 = Date.now();\n duration = this.flipDuration * (1 - this.flip.progress);\n startRatio = this.flip.progress;\n this.flip.auto = true;\n this.$emit((\"flip-\" + (this.flip.direction) + \"-start\"), this.page);\n animate = function () {\n return requestAnimationFrame(function () {\n var ratio, t;\n t = Date.now() - t0;\n ratio = startRatio + t / duration;\n if (ratio > 1) {\n ratio = 1;\n }\n this$1$1.flip.progress = ease ? easeInOut(ratio) : ratio;\n if (ratio < 1) {\n return animate();\n } else {\n if (this$1$1.flip.direction !== this$1$1.forwardDirection) {\n this$1$1.currentPage -= this$1$1.displayedPages;\n } else {\n this$1$1.currentPage += this$1$1.displayedPages;\n }\n this$1$1.$emit((\"flip-\" + (this$1$1.flip.direction) + \"-end\"), this$1$1.page);\n if (this$1$1.displayedPages === 1 && this$1$1.flip.direction === this$1$1.forwardDirection) {\n this$1$1.flip.direction = null;\n } else {\n this$1$1.onImageLoad(1, function () {\n return this$1$1.flip.direction = null;\n });\n }\n return this$1$1.flip.auto = false;\n }\n });\n };\n return animate();\n },\n flipRevert: function() {\n var this$1$1 = this;\n\n var animate, duration, startRatio, t0;\n t0 = Date.now();\n duration = this.flipDuration * this.flip.progress;\n startRatio = this.flip.progress;\n this.flip.auto = true;\n animate = function () {\n return requestAnimationFrame(function () {\n var ratio, t;\n t = Date.now() - t0;\n ratio = startRatio - startRatio * t / duration;\n if (ratio < 0) {\n ratio = 0;\n }\n this$1$1.flip.progress = ratio;\n if (ratio > 0) {\n return animate();\n } else {\n this$1$1.firstPage = this$1$1.currentPage;\n this$1$1.secondPage = this$1$1.currentPage + 1;\n if (this$1$1.displayedPages === 1 && this$1$1.flip.direction !== this$1$1.forwardDirection) {\n this$1$1.flip.direction = null;\n } else {\n this$1$1.onImageLoad(1, function () {\n return this$1$1.flip.direction = null;\n });\n }\n return this$1$1.flip.auto = false;\n }\n });\n };\n return animate();\n },\n onImageLoad: function(trigger, cb) {\n this.nImageLoad = 0;\n this.nImageLoadTrigger = trigger;\n return this.imageLoadCallback = cb;\n },\n didLoadImage: function(ev) {\n if (this.imageWidth === null) {\n this.imageWidth = (ev.target || ev.path[0]).naturalWidth;\n this.imageHeight = (ev.target || ev.path[0]).naturalHeight;\n this.preloadImages();\n }\n if (!this.imageLoadCallback) {\n return;\n }\n if (++this.nImageLoad >= this.nImageLoadTrigger) {\n this.imageLoadCallback();\n return this.imageLoadCallback = null;\n }\n },\n zoomIn: function(zoomAt) {\n if ( zoomAt === void 0 ) zoomAt = null;\n\n if (!this.canZoomIn) {\n return;\n }\n this.zoomIndex += 1;\n return this.zoomTo(this.zooms_[this.zoomIndex], zoomAt);\n },\n zoomOut: function(zoomAt) {\n if ( zoomAt === void 0 ) zoomAt = null;\n\n if (!this.canZoomOut) {\n return;\n }\n this.zoomIndex -= 1;\n return this.zoomTo(this.zooms_[this.zoomIndex], zoomAt);\n },\n zoomTo: function(zoom, zoomAt) {\n var this$1$1 = this;\n if ( zoomAt === void 0 ) zoomAt = null;\n\n var animate, containerFixedX, containerFixedY, end, endX, endY, fixedX, fixedY, rect, start, startX, startY, t0, viewport;\n viewport = this.$refs.viewport;\n if (zoomAt) {\n rect = viewport.getBoundingClientRect();\n fixedX = zoomAt.pageX - rect.left;\n fixedY = zoomAt.pageY - rect.top;\n } else {\n fixedX = viewport.clientWidth / 2;\n fixedY = viewport.clientHeight / 2;\n }\n start = this.zoom;\n end = zoom;\n startX = viewport.scrollLeft;\n startY = viewport.scrollTop;\n containerFixedX = fixedX + startX;\n containerFixedY = fixedY + startY;\n endX = containerFixedX / start * end - fixedX;\n endY = containerFixedY / start * end - fixedY;\n t0 = Date.now();\n this.zooming = true;\n this.$emit('zoom-start', zoom);\n animate = function () {\n return requestAnimationFrame(function () {\n var ratio, t;\n t = Date.now() - t0;\n ratio = t / this$1$1.zoomDuration;\n if (ratio > 1 || this$1$1.IE) {\n ratio = 1;\n }\n ratio = easeInOut(ratio);\n this$1$1.zoom = start + (end - start) * ratio;\n this$1$1.scrollLeft = startX + (endX - startX) * ratio;\n this$1$1.scrollTop = startY + (endY - startY) * ratio;\n if (t < this$1$1.zoomDuration) {\n return animate();\n } else {\n this$1$1.$emit('zoom-end', zoom);\n this$1$1.zooming = false;\n this$1$1.zoom = zoom;\n this$1$1.scrollLeft = endX;\n return this$1$1.scrollTop = endY;\n }\n });\n };\n animate();\n if (end > 1) {\n return this.preloadImages(true);\n }\n },\n zoomAt: function(zoomAt) {\n this.zoomIndex = (this.zoomIndex + 1) % this.zooms_.length;\n return this.zoomTo(this.zooms_[this.zoomIndex], zoomAt);\n },\n swipeStart: function(touch) {\n this.touchStartX = touch.pageX;\n this.touchStartY = touch.pageY;\n this.maxMove = 0;\n if (this.zoom <= 1) {\n if (this.dragToFlip) {\n return this.activeCursor = 'grab';\n }\n } else {\n this.startScrollLeft = this.$refs.viewport.scrollLeft;\n this.startScrollTop = this.$refs.viewport.scrollTop;\n return this.activeCursor = 'all-scroll';\n }\n },\n swipeMove: function(touch) {\n var x, y;\n if (this.touchStartX == null) {\n return;\n }\n x = touch.pageX - this.touchStartX;\n y = touch.pageY - this.touchStartY;\n this.maxMove = Math.max(this.maxMove, Math.abs(x));\n this.maxMove = Math.max(this.maxMove, Math.abs(y));\n if (this.zoom > 1) {\n if (this.dragToScroll) {\n this.dragScroll(x, y);\n }\n return;\n }\n if (!this.dragToFlip) {\n return;\n }\n if (Math.abs(y) > Math.abs(x)) {\n return;\n }\n this.activeCursor = 'grabbing';\n if (x > 0) {\n if (this.flip.direction === null && this.canFlipLeft && x >= this.swipeMin) {\n this.flipStart('left', false);\n }\n if (this.flip.direction === 'left') {\n this.flip.progress = x / this.pageWidth;\n if (this.flip.progress > 1) {\n this.flip.progress = 1;\n }\n }\n } else {\n if (this.flip.direction === null && this.canFlipRight && x <= -this.swipeMin) {\n this.flipStart('right', false);\n }\n if (this.flip.direction === 'right') {\n this.flip.progress = -x / this.pageWidth;\n if (this.flip.progress > 1) {\n this.flip.progress = 1;\n }\n }\n }\n return true;\n },\n swipeEnd: function(touch) {\n if (this.touchStartX == null) {\n return;\n }\n if (this.clickToZoom && this.maxMove < this.swipeMin) {\n this.zoomAt(touch);\n }\n if (this.flip.direction !== null && !this.flip.auto) {\n if (this.flip.progress > 1 / 4) {\n this.flipAuto(false);\n } else {\n this.flipRevert();\n }\n }\n this.touchStartX = null;\n return this.activeCursor = null;\n },\n onTouchStart: function(ev) {\n this.hasTouchEvents = true;\n return this.swipeStart(ev.changedTouches[0]);\n },\n onTouchMove: function(ev) {\n if (this.swipeMove(ev.changedTouches[0])) {\n if (ev.cancelable) {\n return ev.preventDefault();\n }\n }\n },\n onTouchEnd: function(ev) {\n return this.swipeEnd(ev.changedTouches[0]);\n },\n onPointerDown: function(ev) {\n this.hasPointerEvents = true;\n if (this.hasTouchEvents) {\n return;\n }\n if (ev.which && ev.which !== 1) { // Ignore right-click\n return;\n }\n this.swipeStart(ev);\n try {\n return ev.target.setPointerCapture(ev.pointerId);\n } catch (error) {\n\n }\n },\n onPointerMove: function(ev) {\n if (!this.hasTouchEvents) {\n return this.swipeMove(ev);\n }\n },\n onPointerUp: function(ev) {\n if (this.hasTouchEvents) {\n return;\n }\n this.swipeEnd(ev);\n try {\n return ev.target.releasePointerCapture(ev.pointerId);\n } catch (error) {\n\n }\n },\n onMouseDown: function(ev) {\n if (this.hasTouchEvents || this.hasPointerEvents) {\n return;\n }\n if (ev.which && ev.which !== 1) { // Ignore right-click\n return;\n }\n return this.swipeStart(ev);\n },\n onMouseMove: function(ev) {\n if (!(this.hasTouchEvents || this.hasPointerEvents)) {\n return this.swipeMove(ev);\n }\n },\n onMouseUp: function(ev) {\n if (!(this.hasTouchEvents || this.hasPointerEvents)) {\n return this.swipeEnd(ev);\n }\n },\n dragScroll: function(x, y) {\n this.scrollLeft = this.startScrollLeft - x;\n return this.scrollTop = this.startScrollTop - y;\n },\n onWheel: function(ev) {\n if (this.wheel === 'scroll' && this.zoom > 1 && this.dragToScroll) {\n this.scrollLeft = this.$refs.viewport.scrollLeft + ev.deltaX;\n this.scrollTop = this.$refs.viewport.scrollTop + ev.deltaY;\n if (ev.cancelable) {\n ev.preventDefault();\n }\n }\n if (this.wheel === 'zoom') {\n if (ev.deltaY >= 100) {\n this.zoomOut(ev);\n return ev.preventDefault();\n } else if (ev.deltaY <= -100) {\n this.zoomIn(ev);\n return ev.preventDefault();\n }\n }\n },\n preloadImages: function(hiRes) {\n if ( hiRes === void 0 ) hiRes = false;\n\n var i, j, k, ref, ref1, ref2, ref3, src;\n for (i = j = ref = this.currentPage - 3, ref1 = this.currentPage + 3; (ref <= ref1 ? j <= ref1 : j >= ref1); i = ref <= ref1 ? ++j : --j) {\n this.pageUrlLoading(i); // this preloads image\n }\n if (hiRes) {\n for (i = k = ref2 = this.currentPage, ref3 = this.currentPage + this.displayedPages; (ref2 <= ref3 ? k < ref3 : k > ref3); i = ref2 <= ref3 ? ++k : --k) {\n src = this.pagesHiRes[i];\n if (src) {\n (new Image()).src = src;\n }\n }\n }\n },\n goToPage: function(p) {\n if (p === null || p === this.page) {\n return;\n }\n if (this.pages[0] === null) {\n if (this.displayedPages === 2 && p === 1) {\n this.currentPage = 0;\n } else {\n this.currentPage = p;\n }\n } else {\n this.currentPage = p - 1;\n }\n this.minX = 2e308;\n this.maxX = -2e308;\n return this.currentCenterOffset = this.centerOffset;\n },\n loadImage: function(url) {\n var this$1$1 = this;\n\n var img;\n if (this.imageWidth === null) {\n // First loaded image defines the image width and height.\n // So it must be true image, not 'loading' image.\n return url;\n } else {\n if (this.loadedImages[url]) {\n return url;\n } else {\n img = new Image();\n img.onload = function () {\n if (this$1$1.$set) {\n return this$1$1.$set(this$1$1.loadedImages, url, true);\n } else {\n return this$1$1.loadedImages[url] = true;\n }\n };\n img.src = url;\n return this.loadingImage;\n }\n }\n }\n },\n watch: {\n currentPage: function() {\n this.firstPage = this.currentPage;\n this.secondPage = this.currentPage + 1;\n return this.preloadImages();\n },\n centerOffset: function() {\n var this$1$1 = this;\n\n var animate;\n if (this.animatingCenter) {\n return;\n }\n animate = function () {\n return requestAnimationFrame(function () {\n var diff, rate;\n rate = 0.1;\n diff = this$1$1.centerOffset - this$1$1.currentCenterOffset;\n if (Math.abs(diff) < 0.5) {\n this$1$1.currentCenterOffset = this$1$1.centerOffset;\n return this$1$1.animatingCenter = false;\n } else {\n this$1$1.currentCenterOffset += diff * rate;\n return animate();\n }\n });\n };\n this.animatingCenter = true;\n return animate();\n },\n scrollLeftLimited: function(val) {\n var this$1$1 = this;\n\n if (this.IE) {\n return requestAnimationFrame(function () {\n return this$1$1.$refs.viewport.scrollLeft = val;\n });\n } else {\n return this.$refs.viewport.scrollLeft = val;\n }\n },\n scrollTopLimited: function(val) {\n var this$1$1 = this;\n\n if (this.IE) {\n return requestAnimationFrame(function () {\n return this$1$1.$refs.viewport.scrollTop = val;\n });\n } else {\n return this.$refs.viewport.scrollTop = val;\n }\n },\n pages: function(after, before) {\n this.fixFirstPage();\n if (!(before != null ? before.length : void 0) && (after != null ? after.length : void 0)) {\n if (this.startPage > 1 && after[0] === null) {\n return this.currentPage++;\n }\n }\n },\n startPage: function(p) {\n return this.goToPage(p);\n }\n }\n};\n\nfunction normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {\r\n if (typeof shadowMode !== 'boolean') {\r\n createInjectorSSR = createInjector;\r\n createInjector = shadowMode;\r\n shadowMode = false;\r\n }\r\n // Vue.extend constructor export interop.\r\n var options = typeof script === 'function' ? script.options : script;\r\n // render functions\r\n if (template && template.render) {\r\n options.render = template.render;\r\n options.staticRenderFns = template.staticRenderFns;\r\n options._compiled = true;\r\n // functional template\r\n if (isFunctionalTemplate) {\r\n options.functional = true;\r\n }\r\n }\r\n // scopedId\r\n if (scopeId) {\r\n options._scopeId = scopeId;\r\n }\r\n var hook;\r\n if (moduleIdentifier) {\r\n // server build\r\n hook = function (context) {\r\n // 2.3 injection\r\n context =\r\n context || // cached call\r\n (this.$vnode && this.$vnode.ssrContext) || // stateful\r\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional\r\n // 2.2 with runInNewContext: true\r\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\r\n context = __VUE_SSR_CONTEXT__;\r\n }\r\n // inject component styles\r\n if (style) {\r\n style.call(this, createInjectorSSR(context));\r\n }\r\n // register component module identifier for async chunk inference\r\n if (context && context._registeredComponents) {\r\n context._registeredComponents.add(moduleIdentifier);\r\n }\r\n };\r\n // used by ssr in case component is cached and beforeCreate\r\n // never gets called\r\n options._ssrRegister = hook;\r\n }\r\n else if (style) {\r\n hook = shadowMode\r\n ? function (context) {\r\n style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));\r\n }\r\n : function (context) {\r\n style.call(this, createInjector(context));\r\n };\r\n }\r\n if (hook) {\r\n if (options.functional) {\r\n // register for functional component in vue file\r\n var originalRender = options.render;\r\n options.render = function renderWithStyleInjection(h, context) {\r\n hook.call(context);\r\n return originalRender(h, context);\r\n };\r\n }\r\n else {\r\n // inject component registration as beforeCreate hook\r\n var existing = options.beforeCreate;\r\n options.beforeCreate = existing ? [].concat(existing, hook) : [hook];\r\n }\r\n }\r\n return script;\r\n}\n\nvar isOldIE = typeof navigator !== 'undefined' &&\r\n /msie [6-9]\\\\b/.test(navigator.userAgent.toLowerCase());\r\nfunction createInjector(context) {\r\n return function (id, style) { return addStyle(id, style); };\r\n}\r\nvar HEAD;\r\nvar styles = {};\r\nfunction addStyle(id, css) {\r\n var group = isOldIE ? css.media || 'default' : id;\r\n var style = styles[group] || (styles[group] = { ids: new Set(), styles: [] });\r\n if (!style.ids.has(id)) {\r\n style.ids.add(id);\r\n var code = css.source;\r\n if (css.map) {\r\n // https://developer.chrome.com/devtools/docs/javascript-debugging\r\n // this makes source maps inside style tags work properly in Chrome\r\n code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */';\r\n // http://stackoverflow.com/a/26603875\r\n code +=\r\n '\\n/*# sourceMappingURL=data:application/json;base64,' +\r\n btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +\r\n ' */';\r\n }\r\n if (!style.element) {\r\n style.element = document.createElement('style');\r\n style.element.type = 'text/css';\r\n if (css.media)\r\n { style.element.setAttribute('media', css.media); }\r\n if (HEAD === undefined) {\r\n HEAD = document.head || document.getElementsByTagName('head')[0];\r\n }\r\n HEAD.appendChild(style.element);\r\n }\r\n if ('styleSheet' in style.element) {\r\n style.styles.push(code);\r\n style.element.styleSheet.cssText = style.styles\r\n .filter(Boolean)\r\n .join('\\n');\r\n }\r\n else {\r\n var index = style.ids.size - 1;\r\n var textNode = document.createTextNode(code);\r\n var nodes = style.element.childNodes;\r\n if (nodes[index])\r\n { style.element.removeChild(nodes[index]); }\r\n if (nodes.length)\r\n { style.element.insertBefore(textNode, nodes[index]); }\r\n else\r\n { style.element.appendChild(textNode); }\r\n }\r\n }\r\n}\n\n/* script */\nvar __vue_script__ = script;\n\n/* template */\nvar __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_vm._t(\"default\",null,null,{\n canFlipLeft: _vm.canFlipLeft,\n canFlipRight: _vm.canFlipRight,\n canZoomIn: _vm.canZoomIn,\n canZoomOut: _vm.canZoomOut,\n page: _vm.page,\n numPages: _vm.numPages,\n flipLeft: _vm.flipLeft,\n flipRight: _vm.flipRight,\n zoomIn: _vm.zoomIn,\n zoomOut: _vm.zoomOut,\n }),_vm._v(\" \"),_c('div',{ref:\"viewport\",staticClass:\"viewport\",class:{\n zoom: _vm.zooming || _vm.zoom > 1,\n 'drag-to-scroll': _vm.dragToScroll,\n },style:({ cursor: _vm.cursor == 'grabbing' ? 'grabbing' : 'auto' }),on:{\"touchmove\":_vm.onTouchMove,\"pointermove\":_vm.onPointerMove,\"mousemove\":_vm.onMouseMove,\"touchend\":_vm.onTouchEnd,\"touchcancel\":_vm.onTouchEnd,\"pointerup\":_vm.onPointerUp,\"pointercancel\":_vm.onPointerUp,\"mouseup\":_vm.onMouseUp,\"wheel\":_vm.onWheel}},[_c('div',{staticClass:\"flipbook-container\",style:({ transform: (\"scale(\" + _vm.zoom + \")\") })},[_c('div',{staticClass:\"click-to-flip left\",style:({ cursor: _vm.canFlipLeft ? 'pointer' : 'auto' }),on:{\"click\":_vm.flipLeft}}),_vm._v(\" \"),_c('div',{staticClass:\"click-to-flip right\",style:({ cursor: _vm.canFlipRight ? 'pointer' : 'auto' }),on:{\"click\":_vm.flipRight}}),_vm._v(\" \"),_c('div',{style:({ transform: (\"translateX(\" + _vm.centerOffsetSmoothed + \"px)\") })},[(_vm.showLeftPage)?_c('img',{staticClass:\"page fixed\",style:({\n width: _vm.pageWidth + 'px',\n height: _vm.pageHeight + 'px',\n left: _vm.xMargin + 'px',\n top: _vm.yMargin + 'px',\n }),attrs:{\"src\":_vm.pageUrlLoading(_vm.leftPage, true)},on:{\"load\":function($event){return _vm.didLoadImage($event)}}}):_vm._e(),_vm._v(\" \"),(_vm.showRightPage)?_c('img',{staticClass:\"page fixed\",style:({\n width: _vm.pageWidth + 'px',\n height: _vm.pageHeight + 'px',\n left: _vm.viewWidth / 2 + 'px',\n top: _vm.yMargin + 'px',\n }),attrs:{\"src\":_vm.pageUrlLoading(_vm.rightPage, true)},on:{\"load\":function($event){return _vm.didLoadImage($event)}}}):_vm._e(),_vm._v(\" \"),_c('div',{style:({ opacity: _vm.flip.opacity })},_vm._l((_vm.polygonArray),function(ref){\n var key = ref[0];\n var bgImage = ref[1];\n var lighting = ref[2];\n var bgPos = ref[3];\n var transform = ref[4];\n var z = ref[5];\nreturn _c('div',{key:key,staticClass:\"polygon\",class:{ blank: !bgImage },style:({\n backgroundImage: bgImage && (\"url(\" + (_vm.loadImage(bgImage)) + \")\"),\n backgroundSize: _vm.polygonBgSize,\n backgroundPosition: bgPos,\n width: _vm.polygonWidth,\n height: _vm.polygonHeight,\n transform: transform,\n zIndex: z,\n })},[_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(lighting.length),expression:\"lighting.length\"}],staticClass:\"lighting\",style:({ backgroundImage: lighting })})])}),0),_vm._v(\" \"),_c('div',{staticClass:\"bounding-box\",style:({\n left: _vm.boundingLeft + 'px',\n top: _vm.yMargin + 'px',\n width: _vm.boundingRight - _vm.boundingLeft + 'px',\n height: _vm.pageHeight + 'px',\n cursor: _vm.cursor,\n }),on:{\"touchstart\":_vm.onTouchStart,\"pointerdown\":_vm.onPointerDown,\"mousedown\":_vm.onMouseDown}})])])])],2)};\nvar __vue_staticRenderFns__ = [];\n\n /* style */\n var __vue_inject_styles__ = function (inject) {\n if (!inject) { return }\n inject(\"data-v-e3f0fbe2_0\", { source: \".viewport[data-v-e3f0fbe2]{-webkit-overflow-scrolling:touch;width:100%;height:100%}.viewport.zoom[data-v-e3f0fbe2]{overflow:scroll}.viewport.zoom.drag-to-scroll[data-v-e3f0fbe2]{overflow:hidden}.flipbook-container[data-v-e3f0fbe2]{position:relative;width:100%;height:100%;transform-origin:top left;user-select:none}.click-to-flip[data-v-e3f0fbe2]{position:absolute;width:50%;height:100%;top:0;user-select:none}.click-to-flip.left[data-v-e3f0fbe2]{left:0}.click-to-flip.right[data-v-e3f0fbe2]{right:0}.bounding-box[data-v-e3f0fbe2]{position:absolute;user-select:none}.page[data-v-e3f0fbe2]{position:absolute;backface-visibility:hidden}.polygon[data-v-e3f0fbe2]{position:absolute;top:0;left:0;background-repeat:no-repeat;backface-visibility:hidden;transform-origin:center left}.polygon.blank[data-v-e3f0fbe2]{background-color:#ddd}.polygon .lighting[data-v-e3f0fbe2]{width:100%;height:100%}\", map: undefined, media: undefined });\n\n };\n /* scoped */\n var __vue_scope_id__ = \"data-v-e3f0fbe2\";\n /* module identifier */\n var __vue_module_identifier__ = undefined;\n /* functional template */\n var __vue_is_functional_template__ = false;\n /* style inject SSR */\n \n /* style inject shadow dom */\n \n\n \n var __vue_component__ = /*#__PURE__*/normalizeComponent(\n { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },\n __vue_inject_styles__,\n __vue_script__,\n __vue_scope_id__,\n __vue_is_functional_template__,\n __vue_module_identifier__,\n false,\n createInjector,\n undefined,\n undefined\n );\n\nexport { __vue_component__ as default };\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"catalogue-drawer-container\"},[(_vm.visible)?_c('div',{staticClass:\"drawer-overlay\",on:{\"click\":_vm.closeDrawer}}):_vm._e(),_c('div',{staticClass:\"catalogue-drawer\",class:{ 'drawer-open': _vm.visible },style:({ width: _vm.drawerWidth })},[_c('div',{staticClass:\"drawer-header\"},[_c('h3',{staticClass:\"drawer-title\"},[_vm._v(\"目录\")]),_c('button',{staticClass:\"close-button\",on:{\"click\":_vm.closeDrawer}},[_c('span',{staticClass:\"close-icon\"},[_vm._v(\"×\")])])]),_c('div',{staticClass:\"page-jump-section\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.jumpPageInput),expression:\"jumpPageInput\"}],staticClass:\"page-jump-input\",attrs:{\"type\":\"text\",\"placeholder\":\"请输入页码\"},domProps:{\"value\":(_vm.jumpPageInput)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.jumpPageInput=$event.target.value},_vm.handlePageInput],\"focus\":_vm.onInputFocus,\"blur\":_vm.onInputBlur}}),_c('button',{staticClass:\"btn-jump\",on:{\"click\":_vm.handleJumpToPage}},[_vm._v(\" 跳转 \")])]),_c('div',{staticClass:\"drawer-content\"},[(_vm.loading)?_c('div',{staticClass:\"loading-container\"},[_c('div',{staticClass:\"loading-spinner\"}),_c('p',[_vm._v(\"加载目录中...\")])]):(_vm.error)?_c('div',{staticClass:\"error-container\"},[_c('div',{staticClass:\"error-icon\"},[_vm._v(\"⚠️\")]),_c('p',[_vm._v(\"目录加载失败\")]),_c('button',{staticClass:\"retry-button\",on:{\"click\":_vm.fetchCatalogue}},[_vm._v(\" 重新加载 \")])]):(_vm.catalogueData && _vm.catalogueData.length > 0)?_c('div',{staticClass:\"catalogue-list\"},_vm._l((_vm.catalogueData),function(item,index){return _c('catalogue-item',{key:`catalogue-${index}`,attrs:{\"item\":item || {},\"level\":0},on:{\"item-click\":_vm.onItemClick}})}),1):_c('div',{staticClass:\"empty-container\"},[_c('p',[_vm._v(\"暂无目录\")])])])])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","<template>\r\n <div class=\"catalogue-drawer-container\">\r\n <!-- 遮罩层 -->\r\n <div v-if=\"visible\" class=\"drawer-overlay\" @click=\"closeDrawer\"></div>\r\n \r\n <!-- 抽屉主体 -->\r\n <div \r\n class=\"catalogue-drawer\" \r\n :class=\"{ 'drawer-open': visible }\"\r\n :style=\"{ width: drawerWidth }\"\r\n >\r\n <div class=\"drawer-header\">\r\n <h3 class=\"drawer-title\">目录</h3>\r\n <button class=\"close-button\" @click=\"closeDrawer\">\r\n <span class=\"close-icon\">×</span>\r\n </button>\r\n </div>\r\n\r\n <!-- 页码跳转 -->\r\n <div class=\"page-jump-section\">\r\n <input\r\n type=\"text\"\r\n v-model=\"jumpPageInput\"\r\n @input=\"handlePageInput\"\r\n placeholder=\"请输入页码\"\r\n class=\"page-jump-input\"\r\n @focus=\"onInputFocus\"\r\n @blur=\"onInputBlur\"\r\n />\r\n <button @click=\"handleJumpToPage\" class=\"btn-jump\">\r\n 跳转\r\n </button>\r\n </div>\r\n\r\n <div class=\"drawer-content\">\r\n <!-- 加载状态 -->\r\n <div v-if=\"loading\" class=\"loading-container\">\r\n <div class=\"loading-spinner\"></div>\r\n <p>加载目录中...</p>\r\n </div>\r\n \r\n <!-- 错误状态 -->\r\n <div v-else-if=\"error\" class=\"error-container\">\r\n <div class=\"error-icon\">⚠️</div>\r\n <p>目录加载失败</p>\r\n <button class=\"retry-button\" @click=\"fetchCatalogue\">\r\n 重新加载\r\n </button>\r\n </div>\r\n \r\n <!-- 目录内容 -->\r\n <div v-else-if=\"catalogueData && catalogueData.length > 0\" class=\"catalogue-list\">\r\n <catalogue-item\r\n v-for=\"(item, index) in catalogueData\"\r\n :key=\"`catalogue-${index}`\"\r\n :item=\"item || {}\"\r\n :level=\"0\"\r\n @item-click=\"onItemClick\"\r\n />\r\n </div>\r\n \r\n <!-- 空状态 -->\r\n <div v-else class=\"empty-container\">\r\n <p>暂无目录</p>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script>\r\n// 目录项组件\r\nconst CatalogueItem = {\r\n name: 'CatalogueItem',\r\n props: {\r\n item: {\r\n type: Object,\r\n required: true\r\n },\r\n level: {\r\n type: Number,\r\n default: 0\r\n }\r\n },\r\n data() {\r\n return {\r\n expanded: false\r\n }\r\n },\r\n computed: {\r\n hasChildren() {\r\n const children = this.item.childrenList || this.item.children || this.item.subItems || this.item.items\r\n return children && children.length > 0\r\n },\r\n childrenData() {\r\n return this.item.childrenList || this.item.children || this.item.subItems || this.item.items || []\r\n },\r\n itemStyle() {\r\n // 移除缩进,所有层级都左对齐\r\n return {\r\n paddingLeft: '12px',\r\n paddingRight: '12px'\r\n }\r\n },\r\n levelClass() {\r\n return `level-${this.level}`\r\n }\r\n },\r\n methods: {\r\n toggleExpand() {\r\n if (this.hasChildren) {\r\n this.expanded = !this.expanded\r\n } else {\r\n this.handleClick()\r\n }\r\n },\r\n handleClick() {\r\n this.$emit('item-click', this.item)\r\n }\r\n },\r\n render(h) {\r\n const item = this.item\r\n const titleText = item.titleName || item.title || item.name || item.text || item.label || '未命名章节'\r\n const pageNum = item.startPageNum || item.pageNumber || item.page || item.pageNum\r\n \r\n // 创建拖拽手柄\r\n const dragHandle = h('span', {\r\n class: 'drag-handle'\r\n }, '')\r\n \r\n // 创建展开图标\r\n const expandIcon = this.hasChildren ? h('span', {\r\n class: 'expand-icon'\r\n }, this.expanded ? '▼' : '▶') : null\r\n \r\n // 创建层级指示器\r\n const levelIndicator = this.level > 0 ? h('span', {\r\n class: `level-indicator level-${this.level}`\r\n }, '•'.repeat(this.level)) : null\r\n \r\n // 创建标题\r\n const titleSpan = h('span', {\r\n class: 'item-title'\r\n }, titleText)\r\n \r\n // 创建页码(放在标题后面)\r\n // const pageSpan = pageNum ? h('span', {\r\n // class: 'page-number'\r\n // }, `第${pageNum}页`) : null\r\n const pageSpan = null\r\n // 创建子级目录\r\n const childrenDiv = (this.hasChildren && this.expanded) ? h('div', {\r\n class: 'children-container'\r\n }, this.childrenData.map((child, index) => {\r\n return h('catalogue-item', {\r\n key: index,\r\n props: {\r\n item: child,\r\n level: this.level + 1\r\n },\r\n on: {\r\n 'item-click': (item) => this.$emit('item-click', item)\r\n }\r\n })\r\n })) : null\r\n \r\n return h('div', {\r\n class: 'catalogue-item-wrapper'\r\n }, [\r\n h('div', {\r\n class: [\r\n 'catalogue-item',\r\n this.levelClass,\r\n {\r\n 'has-children': this.hasChildren,\r\n 'expanded': this.expanded\r\n }\r\n ],\r\n style: this.itemStyle,\r\n on: {\r\n click: this.toggleExpand\r\n }\r\n }, [\r\n h('div', {\r\n class: 'item-content'\r\n }, [dragHandle, expandIcon, levelIndicator, titleSpan, pageSpan].filter(Boolean))\r\n ]),\r\n childrenDiv\r\n ].filter(Boolean))\r\n }\r\n}\r\n\r\nexport default {\r\n name: 'BookCatalogueDrawer',\r\n components: {\r\n CatalogueItem\r\n },\r\n props: {\r\n value: {\r\n type: Boolean,\r\n default: false\r\n },\r\n bookId: {\r\n type: String,\r\n default: ''\r\n },\r\n catalogue: {\r\n type: Array,\r\n default: () => []\r\n },\r\n loading: {\r\n type: Boolean,\r\n default: false\r\n }\r\n },\r\n data() {\r\n return {\r\n catalogueData: [],\r\n error: null,\r\n jumpPageInput: ''\r\n }\r\n },\r\n computed: {\r\n visible: {\r\n get() {\r\n return this.value\r\n },\r\n set(val) {\r\n this.$emit('input', val)\r\n }\r\n },\r\n drawerWidth() {\r\n // 响应式宽度\r\n return window.innerWidth <= 768 ? '80%' : '400px'\r\n }\r\n },\r\n watch: {\r\n visible(newVal) {\r\n if (newVal && this.bookId && !this.catalogueData.length) {\r\n this.$emit('fetch-catalogue', this.bookId)\r\n }\r\n },\r\n bookId(newVal) {\r\n if (newVal && this.visible) {\r\n this.$emit('fetch-catalogue', newVal)\r\n }\r\n },\r\n catalogue: {\r\n handler(newVal) {\r\n if (newVal && newVal.length > 0) {\r\n this.catalogueData = this.processCatalogueData(newVal)\r\n }\r\n },\r\n immediate: true\r\n }\r\n },\r\n methods: {\r\n /**\r\n * 获取目录数据 - 触发事件让父组件处理\r\n */\r\n fetchCatalogue() {\r\n if (!this.bookId) {\r\n console.warn('缺少 bookId,无法获取目录')\r\n return\r\n }\r\n this.$emit('fetch-catalogue', this.bookId)\r\n },\r\n\r\n /**\r\n * 处理目录数据,确保数据结构正确\r\n * @param {Array} data - 原始目录数据\r\n * @returns {Array} 处理后的目录数据\r\n */\r\n processCatalogueData(data) {\r\n if (!Array.isArray(data)) {\r\n return []\r\n }\r\n \r\n return data.map(item => {\r\n if (!item || typeof item !== 'object') {\r\n return {\r\n titleName: '未知章节',\r\n startPageNum: null,\r\n childrenList: []\r\n }\r\n }\r\n \r\n // 确保必要的字段存在\r\n const processedItem = {\r\n ...item,\r\n titleName: item.titleName || item.title || item.name || '未命名章节',\r\n startPageNum: item.startPageNum || item.pageNumber || item.page || item.pageNum || null,\r\n childrenList: item.childrenList || item.children || item.subItems || item.items || []\r\n }\r\n \r\n // 递归处理子级\r\n if (processedItem.childrenList && processedItem.childrenList.length > 0) {\r\n processedItem.childrenList = this.processCatalogueData(processedItem.childrenList)\r\n }\r\n \r\n return processedItem\r\n })\r\n },\r\n\r\n /**\r\n * 目录项点击事件\r\n * @param {Object} item - 点击的目录项\r\n */\r\n onItemClick(item) {\r\n console.log('目录项被点击:', item)\r\n \r\n // 发送事件给父组件\r\n this.$emit('catalogue-click', item)\r\n \r\n // 如果有页码信息,可以进行跳转\r\n const pageNumber = item.startPageNum || item.pageNumber || item.page || item.pageNum\r\n if (pageNumber) {\r\n this.$emit('page-jump', pageNumber)\r\n }\r\n },\r\n\r\n /**\r\n * 关闭抽屉\r\n */\r\n closeDrawer() {\r\n this.$emit('input', false)\r\n },\r\n\r\n /**\r\n * 处理页码输入\r\n */\r\n handlePageInput(e) {\r\n const value = e.target.value\r\n const numValue = value.replace(/[^\\d]/g, '')\r\n this.jumpPageInput = numValue\r\n },\r\n\r\n /**\r\n * 执行页码跳转\r\n */\r\n handleJumpToPage() {\r\n const pageNum = parseInt(this.jumpPageInput)\r\n if (pageNum && pageNum >= 1) {\r\n this.$emit('page-jump', pageNum)\r\n this.jumpPageInput = ''\r\n this.closeDrawer()\r\n }\r\n },\r\n\r\n /**\r\n * 输入框获得焦点\r\n */\r\n onInputFocus() {\r\n // 可以在这里添加焦点处理逻辑\r\n },\r\n\r\n /**\r\n * 输入框失去焦点\r\n */\r\n onInputBlur() {\r\n this.$emit('Focus')\r\n },\r\n\r\n /**\r\n * 重置数据\r\n */\r\n resetData() {\r\n this.catalogueData = []\r\n this.error = null\r\n this.loading = false\r\n },\r\n\r\n /**\r\n * 处理窗口大小变化\r\n */\r\n handleResize() {\r\n // 可以在这里添加响应式逻辑\r\n }\r\n },\r\n\r\n mounted() {\r\n // 监听窗口大小变化\r\n window.addEventListener('resize', this.handleResize)\r\n },\r\n\r\n beforeDestroy() {\r\n window.removeEventListener('resize', this.handleResize)\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n/* 容器 */\r\n.catalogue-drawer-container {\r\n position: relative;\r\n}\r\n\r\n/* 遮罩层 */\r\n.drawer-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(0, 0, 0, 0.5);\r\n z-index: 19998;\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n/* 抽屉主体 */\r\n.catalogue-drawer {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n height: 100%;\r\n background: #ffffff;\r\n box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);\r\n transform: translateX(-100%);\r\n transition: transform 0.3s ease;\r\n z-index: 19999;\r\n overflow: hidden;\r\n /* 处理安全区域 */\r\n padding-top: env(safe-area-inset-top);\r\n padding-bottom: env(safe-area-inset-bottom);\r\n}\r\n\r\n.catalogue-drawer.drawer-open {\r\n transform: translateX(0);\r\n}\r\n\r\n.drawer-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px 20px;\r\n border-bottom: 1px solid #e5e5e5;\r\n background: #ffffff;\r\n position: sticky;\r\n top: 0;\r\n z-index: 100;\r\n}\r\n\r\n.drawer-title {\r\n font-size: 18px;\r\n font-weight: 600;\r\n color: #333333;\r\n margin: 0;\r\n}\r\n\r\n.close-button {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n padding: 6px;\r\n border-radius: 4px;\r\n transition: background-color 0.2s ease;\r\n width: 32px;\r\n height: 32px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.close-button:hover {\r\n background: #f5f5f5;\r\n}\r\n\r\n.close-icon {\r\n font-size: 18px;\r\n color: #666;\r\n line-height: 1;\r\n display: block;\r\n}\r\n\r\n.close-button:hover .close-icon {\r\n color: #333;\r\n}\r\n\r\n/* 页码跳转区域 */\r\n.page-jump-section {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 12px 16px;\r\n border-bottom: 1px solid #e5e5e5;\r\n background: #f8f9fa;\r\n}\r\n\r\n.page-jump-input {\r\n flex: 1;\r\n min-width: 0;\r\n padding: 10px 12px;\r\n border: 1px solid #ddd;\r\n border-radius: 6px;\r\n background: #fff;\r\n color: #333;\r\n font-size: 16px;\r\n text-align: center;\r\n outline: none;\r\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\r\n}\r\n\r\n.page-jump-input:focus {\r\n border-color: #1989fa;\r\n box-shadow: 0 0 0 2px rgba(25, 137, 250, 0.1);\r\n}\r\n\r\n.page-jump-input::placeholder {\r\n color: #999;\r\n font-size: 14px;\r\n}\r\n\r\n.btn-jump {\r\n flex-shrink: 0;\r\n padding: 10px 20px;\r\n background: #1989fa;\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 14px;\r\n font-weight: 500;\r\n cursor: pointer;\r\n transition: background 0.2s ease;\r\n white-space: nowrap;\r\n}\r\n\r\n.btn-jump:hover {\r\n background: #0066cc;\r\n}\r\n\r\n.btn-jump:active {\r\n background: #004499;\r\n}\r\n\r\n.drawer-content {\r\n height: calc(100% - 65px);\r\n overflow-y: auto;\r\n -webkit-overflow-scrolling: touch;\r\n}\r\n\r\n.loading-container {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n height: 200px;\r\n flex-direction: column;\r\n gap: 12px;\r\n}\r\n\r\n.loading-spinner {\r\n width: 40px;\r\n height: 40px;\r\n border: 3px solid #f3f3f3;\r\n border-top: 3px solid #1989fa;\r\n border-radius: 50%;\r\n animation: spin 1s linear infinite;\r\n}\r\n\r\n@keyframes spin {\r\n 0% { transform: rotate(0deg); }\r\n 100% { transform: rotate(360deg); }\r\n}\r\n\r\n.error-container,\r\n.empty-container {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 40px 20px;\r\n text-align: left;\r\n}\r\n\r\n.error-icon {\r\n font-size: 48px;\r\n margin-bottom: 16px;\r\n}\r\n\r\n.retry-button {\r\n margin-top: 16px;\r\n background: #1989fa;\r\n color: white;\r\n border: none;\r\n padding: 8px 16px;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n transition: background-color 0.2s ease;\r\n}\r\n\r\n.retry-button:hover {\r\n background: #0066cc;\r\n}\r\n\r\n.catalogue-list {\r\n padding: 0;\r\n}\r\n\r\n.catalogue-item-wrapper {\r\n border-bottom: 1px solid #f0f0f0;\r\n}\r\n\r\n.catalogue-item-wrapper:last-child {\r\n border-bottom: none;\r\n}\r\n\r\n.catalogue-item {\r\n padding: 12px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n position: relative;\r\n min-height: 44px;\r\n border-left: 3px solid transparent;\r\n}\r\n\r\n.catalogue-item:hover {\r\n background-color: #f8f9fa;\r\n}\r\n\r\n.catalogue-item:active {\r\n background-color: #e9ecef;\r\n}\r\n\r\n.item-content {\r\n display: flex;\r\n align-items: center;\r\n flex: 1;\r\n width: 100%;\r\n justify-content: flex-start;\r\n gap: 8px;\r\n}\r\n\r\n/* 拖拽手柄样式 */\r\n.drag-handle {\r\n color: #ccc;\r\n font-size: 14px;\r\n cursor: grab;\r\n transition: color 0.2s ease;\r\n flex-shrink: 0;\r\n width: 20px;\r\n text-align: center;\r\n line-height: 1;\r\n user-select: none;\r\n}\r\n\r\n.drag-handle:hover {\r\n color: #999;\r\n}\r\n\r\n.drag-handle:active {\r\n cursor: grabbing;\r\n color: #666;\r\n}\r\n\r\n/* 层级指示器样式 */\r\n.level-indicator {\r\n font-size: 10px;\r\n color: #999;\r\n flex-shrink: 0;\r\n line-height: 1;\r\n margin-right: 4px;\r\n}\r\n\r\n.level-indicator.level-1 {\r\n color: #007bff;\r\n}\r\n\r\n.level-indicator.level-2 {\r\n color: #28a745;\r\n}\r\n\r\n.level-indicator.level-3 {\r\n color: #ffc107;\r\n}\r\n\r\n.expand-icon {\r\n font-size: 12px;\r\n color: #999;\r\n transition: color 0.2s ease;\r\n flex-shrink: 0;\r\n width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.catalogue-item:hover .expand-icon {\r\n color: #666;\r\n}\r\n\r\n.item-title {\r\n font-size: 14px;\r\n color: #333;\r\n line-height: 1.4;\r\n font-weight: 400;\r\n}\r\n\r\n.page-number {\r\n font-size: 12px;\r\n color: #999;\r\n margin-left: 6px;\r\n}\r\n\r\n/* 不同层级的样式 - 用颜色和字体大小区分层级 */\r\n.catalogue-item.level-0 {\r\n background: linear-gradient(90deg, transparent, rgba(0, 123, 255, 0.05), transparent);\r\n}\r\n\r\n.catalogue-item.level-0 .item-title {\r\n font-size: 15px;\r\n color: #333;\r\n font-weight: 600;\r\n}\r\n\r\n.catalogue-item.level-1 {\r\n background: linear-gradient(90deg, transparent, rgba(40, 167, 69, 0.03), transparent);\r\n}\r\n\r\n.catalogue-item.level-1 .item-title {\r\n font-size: 14px;\r\n color: #555;\r\n font-weight: 500;\r\n}\r\n\r\n.catalogue-item.level-2 {\r\n background: linear-gradient(90deg, transparent, rgba(255, 193, 7, 0.03), transparent);\r\n}\r\n\r\n.catalogue-item.level-2 .item-title {\r\n font-size: 13px;\r\n color: #666;\r\n font-weight: 400;\r\n}\r\n\r\n.catalogue-item.level-3 .item-title {\r\n font-size: 12px;\r\n color: #777;\r\n font-weight: 400;\r\n}\r\n\r\n.children-container {\r\n /* 移除背景色,保持一致性 */\r\n background: transparent;\r\n}\r\n\r\n/* 移动端适配 */\r\n@media (max-width: 768px) {\r\n .catalogue-drawer {\r\n /* 移动端安全区域适配 */\r\n padding-top: max(env(safe-area-inset-top), 20px);\r\n padding-bottom: max(env(safe-area-inset-bottom), 20px);\r\n }\r\n \r\n .drawer-header {\r\n padding: 14px 16px;\r\n }\r\n \r\n .drawer-title {\r\n font-size: 16px;\r\n }\r\n \r\n .catalogue-item {\r\n padding: 10px 8px;\r\n min-height: 44px; /* 增加触摸区域 */\r\n }\r\n \r\n .drag-handle {\r\n font-size: 16px; /* 移动端增大手柄 */\r\n width: 24px;\r\n }\r\n \r\n .item-title {\r\n font-size: 13px;\r\n }\r\n \r\n .catalogue-item.level-0 .item-title {\r\n font-size: 14px;\r\n }\r\n \r\n .page-number {\r\n font-size: 11px;\r\n }\r\n \r\n .level-indicator {\r\n font-size: 12px; /* 移动端增大指示器 */\r\n }\r\n}\r\n\r\n/* 滚动条样式 */\r\n.drawer-content::-webkit-scrollbar {\r\n width: 4px;\r\n}\r\n\r\n.drawer-content::-webkit-scrollbar-track {\r\n background: #f5f5f5;\r\n}\r\n\r\n.drawer-content::-webkit-scrollbar-thumb {\r\n background: #ccc;\r\n border-radius: 2px;\r\n}\r\n\r\n.drawer-content::-webkit-scrollbar-thumb:hover {\r\n background: #999;\r\n}\r\n\r\n/* 拖拽状态样式 */\r\n.catalogue-item.dragging {\r\n opacity: 0.5;\r\n transform: scale(0.95);\r\n background: #f0f8ff;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n/* 拖拽悬停目标样式 */\r\n.catalogue-item.drag-over {\r\n border-top: 2px solid #007bff;\r\n background: rgba(0, 123, 255, 0.1);\r\n}\r\n\r\n/* 增强视觉层次 */\r\n.catalogue-item {\r\n border-left: 3px solid transparent;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.catalogue-item.level-0 {\r\n border-left-color: #007bff;\r\n}\r\n\r\n.catalogue-item.level-1 {\r\n border-left-color: #28a745;\r\n}\r\n\r\n.catalogue-item.level-2 {\r\n border-left-color: #ffc107;\r\n}\r\n\r\n.catalogue-item.level-3 {\r\n border-left-color: #dc3545;\r\n}\r\n</style>\r\n","import mod from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookCatalogueDrawer.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookCatalogueDrawer.vue?vue&type=script&lang=js\"","// extracted by mini-css-extract-plugin\nexport {};","export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../node_modules/@vue/vue-loader-v15/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookCatalogueDrawer.vue?vue&type=style&index=0&id=3930be62&prod&scoped=true&lang=css\"","/* globals __VUE_SSR_CONTEXT__ */\n\n// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).\n// This module is a runtime utility for cleaner component module output and will\n// be included in the final webpack user bundle.\n\nexport default function normalizeComponent(\n scriptExports,\n render,\n staticRenderFns,\n functionalTemplate,\n injectStyles,\n scopeId,\n moduleIdentifier /* server only */,\n shadowMode /* vue-cli only */\n) {\n // Vue.extend constructor export interop\n var options =\n typeof scriptExports === 'function' ? scriptExports.options : scriptExports\n\n // render functions\n if (render) {\n options.render = render\n options.staticRenderFns = staticRenderFns\n options._compiled = true\n }\n\n // functional template\n if (functionalTemplate) {\n options.functional = true\n }\n\n // scopedId\n if (scopeId) {\n options._scopeId = 'data-v-' + scopeId\n }\n\n var hook\n if (moduleIdentifier) {\n // server build\n hook = function (context) {\n // 2.3 injection\n context =\n context || // cached call\n (this.$vnode && this.$vnode.ssrContext) || // stateful\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional\n // 2.2 with runInNewContext: true\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\n context = __VUE_SSR_CONTEXT__\n }\n // inject component styles\n if (injectStyles) {\n injectStyles.call(this, context)\n }\n // register component module identifier for async chunk inferrence\n if (context && context._registeredComponents) {\n context._registeredComponents.add(moduleIdentifier)\n }\n }\n // used by ssr in case component is cached and beforeCreate\n // never gets called\n options._ssrRegister = hook\n } else if (injectStyles) {\n hook = shadowMode\n ? function () {\n injectStyles.call(\n this,\n (options.functional ? this.parent : this).$root.$options.shadowRoot\n )\n }\n : injectStyles\n }\n\n if (hook) {\n if (options.functional) {\n // for template-only hot-reload because in that case the render fn doesn't\n // go through the normalizer\n options._injectStyles = hook\n // register for functional component in vue file\n var originalRender = options.render\n options.render = function renderWithStyleInjection(h, context) {\n hook.call(context)\n return originalRender(h, context)\n }\n } else {\n // inject component registration as beforeCreate hook\n var existing = options.beforeCreate\n options.beforeCreate = existing ? [].concat(existing, hook) : [hook]\n }\n }\n\n return {\n exports: scriptExports,\n options: options\n }\n}\n","import { render, staticRenderFns } from \"./BookCatalogueDrawer.vue?vue&type=template&id=3930be62&scoped=true\"\nimport script from \"./BookCatalogueDrawer.vue?vue&type=script&lang=js\"\nexport * from \"./BookCatalogueDrawer.vue?vue&type=script&lang=js\"\nimport style0 from \"./BookCatalogueDrawer.vue?vue&type=style&index=0&id=3930be62&prod&scoped=true&lang=css\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"3930be62\",\n null\n \n)\n\nexport default component.exports","<template>\r\n <div\r\n class=\"photo-album-container\"\r\n @touchstart=\"onContainerTouchStart\"\r\n @touchmove=\"onContainerTouchMove\"\r\n @touchend=\"onContainerTouchEnd\"\r\n @dblclick=\"onContainerDoubleClick\"\r\n @click=\"onContentClick\"\r\n >\r\n <div class=\"album-header\" v-if=\"false\">\r\n <h1>氪氪</h1>\r\n <!-- <p>双页模式 - 双击图片放大,滚轮缩放,拖拽翻页</p> -->\r\n </div>\r\n \r\n <div\r\n v-if=\"contentReady\"\r\n class=\"flipbook-wrapper\"\r\n :style=\"{ transform: `scale(${zoomScale}) translate(${translateX}px, ${translateY}px)` }\"\r\n @dblclick=\"onFlipbookDoubleClick\"\r\n @touchstart.capture=\"onFlipbookTouchStart\"\r\n @touchend.capture=\"onFlipbookTouchEnd\"\r\n >\r\n <!-- 仿真翻页模式 -->\r\n <flipbook\r\n v-if=\"flipMode === 'flip' && flag\"\r\n :class=\"['flipbook', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n :pages=\"pages\"\r\n :pagesHiRes=\"pagesHiRes\"\r\n :startPage=\"startPage\"\r\n v-slot=\"{ page, canFlipLeft, canFlipRight }\"\r\n ref=\"flipbook\"\r\n @flip-left-end=\"onFlipLeftEnd\"\r\n @flip-right-end=\"onFlipRightEnd\"\r\n :zooms=\"null\"\r\n :ambient-light=\"0.6\"\r\n :gloss=\"0.4\"\r\n :single-page=\"isMobile\"\r\n :click-to-flip=\"false\"\r\n :wheel-to-flip=\"!isMobile\"\r\n :swipe-to-flip=\"true\"\r\n :center-pages=\"true\"\r\n >\r\n </flipbook>\r\n \r\n <!-- 滑动翻页模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'slide'\"\r\n :class=\"['slide-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"slideViewer\"\r\n @touchstart=\"onSlideViewerTouchStart\"\r\n @touchmove=\"onSlideViewerTouchMove\"\r\n @touchend=\"onSlideViewerTouchEnd\"\r\n >\r\n <div class=\"slide-container\" :style=\"slideContainerStyle\">\r\n <div\r\n v-for=\"(page, index) in pages\"\r\n :key=\"index\"\r\n class=\"slide-page\"\r\n :class=\"{ 'slide-page-active': index + 1 === currentPage }\"\r\n >\r\n <img v-if=\"page\" :src=\"page\" alt=\"\" class=\"slide-page-image\" />\r\n <div v-else class=\"slide-page-placeholder\">封面/封底</div>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 淡入淡出翻页模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'fade'\"\r\n :class=\"['fade-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"fadeViewer\"\r\n >\r\n <transition name=\"fade-page\" mode=\"out-in\">\r\n <div :key=\"currentPage\" class=\"fade-page-container\">\r\n <img v-if=\"pages[currentPage - 1]\" :src=\"pages[currentPage - 1]\" alt=\"\" class=\"fade-page-image\" />\r\n <div v-else class=\"fade-page-placeholder\">封面/封底</div>\r\n </div>\r\n </transition>\r\n </div>\r\n \r\n <!-- 垂直滚动模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'scroll'\"\r\n :class=\"['scroll-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"scrollViewer\"\r\n @scroll=\"onScrollViewerScroll\"\r\n >\r\n <div class=\"scroll-container\">\r\n <div\r\n v-for=\"(page, index) in pages\"\r\n :key=\"index\"\r\n class=\"scroll-page\"\r\n :ref=\"'scrollPage' + index\"\r\n >\r\n <img v-if=\"page\" :src=\"page\" alt=\"\" class=\"scroll-page-image\" />\r\n <div v-else class=\"scroll-page-placeholder\">封面/封底</div>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 截断特效模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'clip'\"\r\n :class=\"['clip-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"clipViewer\"\r\n >\r\n <div class=\"clip-pages-wrapper\">\r\n <!-- 当前页 -->\r\n <transition name=\"clip-current\">\r\n <div :key=\"'current-' + currentPage\" class=\"clip-page clip-page-current\">\r\n <img v-if=\"pages[currentPage - 1]\" :src=\"pages[currentPage - 1]\" alt=\"\" class=\"clip-page-image\" />\r\n <div v-else class=\"clip-page-placeholder\">封面/封底</div>\r\n </div>\r\n </transition>\r\n <!-- 下一页预览(部分可见) -->\r\n <div v-if=\"currentPage < totalPages\" class=\"clip-page clip-page-next\">\r\n <img v-if=\"pages[currentPage]\" :src=\"pages[currentPage]\" alt=\"\" class=\"clip-page-image\" />\r\n <div v-else class=\"clip-page-placeholder\">封面/封底</div>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 卡片风格模式 -->\r\n <div\r\n v-else-if=\"flipMode === 'card'\"\r\n :class=\"['card-viewer', isMobile ? 'mobile-mode mobile-optimized' : 'desktop-mode desktop-optimized']\"\r\n ref=\"cardViewer\"\r\n >\r\n <div class=\"card-stack\">\r\n <!-- 显示前后各一页的卡片堆叠效果 -->\r\n <transition-group name=\"card-flip\" tag=\"div\" class=\"card-transition-group\">\r\n <div\r\n v-for=\"(page, index) in visibleCards\"\r\n :key=\"'card-' + page.originalIndex\"\r\n class=\"card-item\"\r\n :class=\"{\r\n 'card-item-prev': page.position === 'prev',\r\n 'card-item-current': page.position === 'current',\r\n 'card-item-next': page.position === 'next'\r\n }\"\r\n :style=\"getCardStyle(page.position)\"\r\n >\r\n <div class=\"card-content\">\r\n <img v-if=\"page.src\" :src=\"page.src\" alt=\"\" class=\"card-image\" />\r\n <div v-else class=\"card-placeholder\">封面/封底</div>\r\n </div>\r\n <div class=\"card-page-number\">{{ page.originalIndex + 1 }} / {{ totalPages }}</div>\r\n </div>\r\n </transition-group>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"controls\" :class=\"{ 'mobile-controls': isMobile, 'desktop-controls': !isMobile, 'controls-visible': showControls }\" @click.stop>\r\n <button @click=\"flipLeft\" class=\"btn btn-prev\">\r\n ← 上一页\r\n </button>\r\n <span class=\"page-indicator\">\r\n <span v-if=\"totalPages === 0\">加载中...</span>\r\n <span v-else-if=\"currentPage === 1 && totalPages > 1\">封面</span>\r\n <span v-else-if=\"currentPage === totalPages && totalPages > 1\">封底</span>\r\n <span v-else-if=\"!isMobile && totalPages > 2\">第 {{ Math.ceil((currentPage - 1) / 2) }} 组 / 共 {{ Math.ceil((totalPages - 2) / 2) }} 组</span>\r\n <span v-else-if=\"isMobile && totalPages > 2\">第 {{ Math.max(1, currentPage ) }} 页 / 共 {{ Math.max(0, totalPages - 2) }} 页</span>\r\n <span v-else-if=\"totalPages === 1\">第 1 页 / 共 1 页</span>\r\n <span v-else>第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span>\r\n </span>\r\n <button @click=\"flipRight\" class=\"btn btn-next\">\r\n 下一页 →\r\n </button>\r\n </div>\r\n\r\n <!-- 页码跳转 - 放在翻页控件下方 -->\r\n <!-- <div class=\"page-jump-container\" :class=\"{ 'mobile-page-jump': isMobile }\" @click.stop @touchstart.stop @touchmove.stop @touchend.stop @mousedown.stop @mouseup.stop>\r\n <input\r\n type=\"text\"\r\n :value=\"jumpPageInput\"\r\n @input=\"handlePageInput\"\r\n :placeholder=\"`搜索跳转`\"\r\n class=\"page-jump-input\"\r\n @click.stop\r\n @mousedown.stop\r\n @mouseup.stop\r\n />\r\n <button @click.stop=\"handleJumpToPage\" @mousedown.stop @mouseup.stop class=\"btn btn-jump\">\r\n 跳转\r\n </button>\r\n </div> -->\r\n\r\n <!-- 翻页模式切换按钮(当URL没有指定模式时显示) -->\r\n <div v-if=\"showFlipModeSelector\" class=\"flip-mode-selector\" :class=\"{ 'mobile-flip-mode-selector': isMobile }\">\r\n <button @click=\"toggleFlipModeMenu\" class=\"btn btn-flip-mode\">\r\n <span class=\"flip-mode-icon\">{{ flipModeIcon }}</span>\r\n <span class=\"flip-mode-text\">{{ flipModeLabel }}</span>\r\n </button>\r\n <div v-if=\"showFlipModeMenu\" class=\"flip-mode-menu\">\r\n <div\r\n v-for=\"mode in flipModes\"\r\n :key=\"mode.value\"\r\n class=\"flip-mode-item\"\r\n :class=\"{ 'flip-mode-item-active': flipMode === mode.value }\"\r\n @click=\"selectFlipMode(mode.value)\"\r\n >\r\n <span class=\"flip-mode-item-icon\">{{ mode.icon }}</span>\r\n <span class=\"flip-mode-item-label\">{{ mode.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- 目录按钮 -->\r\n <div\r\n class=\"catalogue-button\"\r\n :class=\"{\r\n 'mobile-catalogue-button': isMobile,\r\n 'dragging': catalogueButtonDragging,\r\n 'catalogue-visible': showControls\r\n }\"\r\n :style=\"{\r\n left: catalogueButtonPosition.x + 'px',\r\n top: catalogueButtonPosition.y + 'px'\r\n }\"\r\n @mousedown=\"onCatalogueMouseDown\"\r\n @touchstart=\"onCatalogueTouchStart\"\r\n @touchmove=\"onCatalogueTouchMove\"\r\n @touchend=\"onCatalogueTouchEnd\"\r\n @click.stop\r\n >\r\n <button @click=\"openCatalogue\" class=\"btn btn-catalogue\">\r\n <!-- <span class=\"drag-handle\">⋮⋮</span> -->\r\n <!-- <span class=\"catalogue-icon\">☰</span> -->\r\n <span class=\"catalogue-text\">目录</span>\r\n </button>\r\n </div>\r\n \r\n <!-- 缩放提示 -->\r\n <div class=\"zoom-hint\" :class=\"{ 'show': zoomScale !== 1 }\">\r\n <div class=\"zoom-info\">\r\n <span>缩放: {{ Math.round(zoomScale * 100) }}%</span>\r\n <button @click=\"resetZoom\" class=\"btn-reset-zoom\">重置</button>\r\n </div>\r\n </div>\r\n\r\n <!-- PC端缩放工具栏 -->\r\n <div class=\"zoom-toolbar\">\r\n <button @click=\"zoomOut\" class=\"btn-zoom\" :disabled=\"zoomScale <= 0.5\">-</button>\r\n <button @click=\"zoomIn\" class=\"btn-zoom\" :disabled=\"zoomScale >= 3\">+</button>\r\n </div>\r\n \r\n <!-- 加载状态覆盖层 -->\r\n <div v-if=\"loading\" class=\"loading-overlay\">\r\n <div class=\"loading-container\">\r\n <div class=\"loading-spinner\">\r\n <div class=\"spinner-ring\"></div>\r\n <div class=\"spinner-ring\"></div>\r\n <div class=\"spinner-ring\"></div>\r\n </div>\r\n <div class=\"loading-text\">\r\n <h3>正在加载书籍</h3>\r\n <p>请稍候,正在获取精彩内容...</p>\r\n <div class=\"loading-progress\">\r\n <div class=\"progress-bar\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 错误状态 -->\r\n <div v-if=\"error && !loading\" class=\"error-container\">\r\n <div class=\"error-content\">\r\n <div class=\"error-icon\">⚠️</div>\r\n <h3>加载失败</h3>\r\n <p>{{ error.message || '网络连接异常,请检查网络后重试' }}</p>\r\n <button @click=\"fetchBooksData\" class=\"retry-btn\">\r\n <span class=\"retry-icon\">🔄</span>\r\n 重新加载\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- 成功状态提示(短暂显示) -->\r\n <div v-if=\"booksData && !loading && !error\" class=\"success-toast\" :class=\"{ 'show': showSuccessToast }\">\r\n 书籍加载完成\r\n </div>\r\n\r\n <!-- 书籍目录抽屉 -->\r\n <book-catalogue-drawer\r\n v-model=\"showCatalogueDrawer\"\r\n :book-id=\"bookId\"\r\n :catalogue=\"catalogue\"\r\n :loading=\"catalogueLoading\"\r\n @catalogue-click=\"onCatalogueClick\"\r\n @page-jump=\"onPageJump\"\r\n @Focus=\"onFocus\"\r\n @fetch-catalogue=\"onFetchCatalogue\"\r\n />\r\n\r\n <!-- 调试信息 -->\r\n <!-- <div class=\"debug-info\" style=\"margin-top: 20px; color: #6c757d; text-align: center; font-size: 0.9rem;\">\r\n 当前页: {{ currentPage }} / 总页数: {{ totalPages }} | 设备: {{ isMobile ? '移动端' : 'PC端' }}\r\n <br>\r\n <div v-if=\"booksData\" style=\"margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 5px; font-family: monospace; font-size: 0.8rem; text-align: left; max-height: 200px; overflow-y: auto;\">\r\n <strong>接口返回数据:</strong><br>\r\n {{ JSON.stringify(booksData, null, 2) }}\r\n </div>\r\n </div> -->\r\n <!-- <div class=\"debug-info\" style=\"margin-top: 20px; color: #6c757d; text-align: center; font-size: 0.9rem;\">\r\n 当前页: {{ currentPage }} / 总页数: {{ totalPages }} | 设备: {{ isMobile ? '移动端' : 'PC端' }} | 模式: {{ isMobile ? '单页' : '双页' }}\r\n <br>\r\n </div> -->\r\n\r\n </div>\r\n</template>\r\n\r\n<script>\r\nimport Flipbook from 'flipbook-vue/vue2'\r\nimport BookCatalogueDrawer from './BookCatalogueDrawer.vue'\r\n\r\nexport default {\r\n name: 'PhotoAlbumView',\r\n components: {\r\n Flipbook,\r\n BookCatalogueDrawer\r\n },\r\n props: {\r\n pages: {\r\n type: Array,\r\n default: () => []\r\n },\r\n bookId: {\r\n type: String,\r\n default: ''\r\n },\r\n startPage: {\r\n type: Number,\r\n default: 1\r\n },\r\n catalogue: {\r\n type: Array,\r\n default: () => []\r\n },\r\n catalogueLoading: {\r\n type: Boolean,\r\n default: false\r\n }\r\n },\r\n data() {\r\n return {\r\n currentPage: 1,\r\n flag: true,\r\n // 设备检测\r\n isMobile: false,\r\n // 接口相关数据\r\n booksData: null,\r\n loading: false,\r\n error: null,\r\n showSuccessToast: false,\r\n // 内容就绪状态\r\n contentReady: false,\r\n // 手势缩放相关\r\n zoomScale: 1,\r\n translateX: 0,\r\n translateY: 0,\r\n lastTouchDistance: 0,\r\n lastTouchCenter: { x: 0, y: 0 },\r\n isZooming: false,\r\n isPanning: false,\r\n touchStartTime: 0,\r\n initialTouches: [],\r\n // Flipbook区域双击检测\r\n flipbookTouchStartTime: 0,\r\n flipbookLastTapTime: 0,\r\n // 目录相关\r\n showCatalogueDrawer: false,\r\n // 目录按钮拖拽相关\r\n catalogueButtonDragging: false,\r\n catalogueButtonPosition: { x: 20, y: 20 },\r\n dragStartPosition: { x: 0, y: 0 },\r\n dragOffset: { x: 0, y: 0 },\r\n // 翻页模式相关\r\n flipMode: 'flip', // flip: 仿真翻页, slide: 滑动翻页, fade: 淡入淡出, scroll: 垂直滚动\r\n showFlipModeMenu: false,\r\n flipModes: [\r\n { value: 'flip', label: '仿真翻页', icon: '📖' },\r\n { value: 'slide', label: '滑动翻页', icon: '↔️' },\r\n { value: 'fade', label: '淡入淡出', icon: '✨' },\r\n { value: 'scroll', label: '垂直滚动', icon: '📜' },\r\n { value: 'clip', label: '截断特效', icon: '✂️' },\r\n { value: 'card', label: '卡片风格', icon: '🃏' }\r\n ],\r\n // 滑动模式相关\r\n slideStartX: 0,\r\n slideCurrentX: 0,\r\n slideIsDragging: false,\r\n // URL参数控制\r\n flipModeFromUrl: false, // 翻页模式是否由URL参数指定\r\n // 控件自动隐藏相关\r\n showControls: false,\r\n hideControlsTimer: null,\r\n // 页码跳转相关\r\n jumpPageInput: ''\r\n }\r\n },\r\n computed: {\r\n totalPages() {\r\n return this.pages.length\r\n },\r\n pagesHiRes() {\r\n return this.pages\r\n },\r\n /**\r\n * 翻页模式图标\r\n */\r\n flipModeIcon() {\r\n const mode = this.flipModes.find(m => m.value === this.flipMode)\r\n return mode ? mode.icon : '📖'\r\n },\r\n /**\r\n * 翻页模式标签\r\n */\r\n flipModeLabel() {\r\n const mode = this.flipModes.find(m => m.value === this.flipMode)\r\n return mode ? mode.label : '仿真翻页'\r\n },\r\n /**\r\n * 滑动容器样式\r\n */\r\n slideContainerStyle() {\r\n const offset = -(this.currentPage - 1) * 100\r\n const dragOffset = this.slideIsDragging ? (this.slideCurrentX - this.slideStartX) / 5 : 0\r\n return {\r\n transform: `translateX(calc(${offset}% + ${dragOffset}px))`,\r\n transition: this.slideIsDragging ? 'none' : 'transform 0.3s ease-out'\r\n }\r\n },\r\n /**\r\n * 是否显示翻页模式选择器\r\n */\r\n showFlipModeSelector() {\r\n // 如果翻页模式是由URL参数指定的,则隐藏选择器\r\n return !this.flipModeFromUrl\r\n },\r\n /**\r\n * 卡片模式可见卡片\r\n */\r\n visibleCards() {\r\n const cards = []\r\n // 上一页\r\n if (this.currentPage > 1) {\r\n cards.push({\r\n src: this.pages[this.currentPage - 2],\r\n originalIndex: this.currentPage - 2,\r\n position: 'prev'\r\n })\r\n }\r\n // 当前页\r\n cards.push({\r\n src: this.pages[this.currentPage - 1],\r\n originalIndex: this.currentPage - 1,\r\n position: 'current'\r\n })\r\n // 下一页\r\n if (this.currentPage < this.totalPages) {\r\n cards.push({\r\n src: this.pages[this.currentPage],\r\n originalIndex: this.currentPage,\r\n position: 'next'\r\n })\r\n }\r\n return cards\r\n },\r\n /**\r\n * 计算当前页面显示文本\r\n */\r\n pageDisplayText() {\r\n if (this.totalPages === 0) return '加载中...'\r\n if (this.currentPage === 1 && this.totalPages > 1) return '封面'\r\n if (this.currentPage === this.totalPages && this.totalPages > 1) return '封底'\r\n \r\n if (this.isMobile) {\r\n if (this.totalPages <= 2) return `第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`\r\n return `第 ${Math.max(1, this.currentPage - 1)} 页 / 共 ${Math.max(0, this.totalPages - 2)} 页`\r\n } else {\r\n if (this.totalPages <= 2) return `第 ${this.currentPage} 页 / 共 ${this.totalPages} 页`\r\n return `第 ${Math.ceil((this.currentPage - 1) / 2)} 组 / 共 ${Math.ceil((this.totalPages - 2) / 2)} 组`\r\n }\r\n }\r\n },\r\n methods: {\r\n /**\r\n * 获取书籍数据\r\n */\r\n async fetchBooksData() {\r\n try {\r\n this.loading = true\r\n this.error = null\r\n\r\n // 注释:原来从URL参数获取fileManagementId,现在改为通过props传入\r\n // const fileManagementId = this.$route.params.id || this.$route.query.id || ''\r\n // this.bookId = fileManagementId\r\n\r\n // 注释:原来调用API获取数据,现在改为通过props传入\r\n // const response = await booksApi.getBooksSlice(fileManagementId, textContent, 1, 9999)\r\n\r\n // 触发事件让父组件处理数据获取\r\n this.$emit('fetch-books-data', this.bookId)\r\n\r\n // 如果通过props传入了pages数据,直接使用\r\n if (this.pages && this.pages.length > 0) {\r\n this.booksData = { data: { records: this.pages.map(url => ({ imageUrl: url })) } }\r\n\r\n // 显示成功提示\r\n this.showSuccessToast = true\r\n setTimeout(() => {\r\n this.showSuccessToast = false\r\n }, 3000)\r\n\r\n // 检查URL参数并跳转到指定页码\r\n this.checkUrlPageParameter()\r\n }\r\n \r\n } catch (error) {\r\n console.error('获取书籍数据失败:', error)\r\n this.error = error\r\n \r\n // 显示错误信息\r\n if (error.response) {\r\n console.error('API错误响应:', error.response.data)\r\n } else if (error.request) {\r\n console.error('网络请求失败:', error.request)\r\n } else {\r\n console.error('请求配置错误:', error.message)\r\n }\r\n } finally {\r\n this.loading = false\r\n }\r\n },\r\n\r\n /**\r\n * 根据API数据更新页面\r\n * @param {Array} apiData - API返回的数据\r\n */\r\n updatePagesFromApiData(apiData) {\r\n if (!Array.isArray(apiData)) {\r\n console.warn('API数据格式不正确,期望数组格式')\r\n return\r\n }\r\n \r\n console.log('开始更新页面数据,数据长度:', apiData.length)\r\n \r\n // 这里可以根据实际的API数据结构来处理\r\n // 假设API返回的数据包含图片URL\r\n const newPages = [null] // 保留封面页\r\n \r\n apiData.forEach((item, index) => {\r\n if (item.imageUrl) {\r\n newPages.push(item.imageUrl)\r\n console.log(`添加页面 ${index + 1}:`, item.imageUrl)\r\n }\r\n })\r\n \r\n if (newPages.length > 1) {\r\n this.pages = newPages\r\n console.log('页面数据更新完成,总页数:', this.pages.length)\r\n }\r\n },\r\n\r\n /**\r\n * 检测是否为移动设备\r\n */\r\n detectMobile() {\r\n const userAgent = navigator.userAgent.toLowerCase()\r\n const mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone']\r\n\r\n // 检测 User Agent\r\n const isMobileUA = mobileKeywords.some(keyword => userAgent.includes(keyword))\r\n\r\n // 检测屏幕尺寸\r\n const isMobileScreen = window.innerWidth <= 768\r\n\r\n // 检测触摸支持\r\n const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0\r\n\r\n // 综合判断\r\n this.isMobile = isMobileUA || (isMobileScreen && isTouchDevice)\r\n\r\n console.log('设备检测结果:', {\r\n userAgent: isMobileUA,\r\n screenSize: isMobileScreen,\r\n touchSupport: isTouchDevice,\r\n finalResult: this.isMobile\r\n })\r\n },\r\n\r\n /**\r\n * 预加载首页图片\r\n */\r\n async preloadFirstPage() {\r\n if (!this.pages || this.pages.length === 0) {\r\n this.contentReady = true\r\n return\r\n }\r\n\r\n const firstPageIndex = this.startPage - 1\r\n const firstPageUrl = this.pages[firstPageIndex]\r\n\r\n if (!firstPageUrl) {\r\n this.contentReady = true\r\n return\r\n }\r\n\r\n try {\r\n await new Promise((resolve, reject) => {\r\n const img = new Image()\r\n img.onload = () => resolve()\r\n img.onerror = () => resolve() // 即使失败也继续\r\n img.src = firstPageUrl\r\n\r\n // 超时保护\r\n setTimeout(() => resolve(), 3000)\r\n })\r\n } catch (error) {\r\n console.warn('首页图片预加载失败:', error)\r\n } finally {\r\n this.contentReady = true\r\n }\r\n },\r\n \r\n /**\r\n * 监听窗口尺寸变化\r\n */\r\n handleResize() {\r\n const wasMobile = this.isMobile\r\n this.detectMobile()\r\n \r\n // 如果设备类型发生变化,重新初始化flipbook\r\n if (wasMobile !== this.isMobile) {\r\n console.log('设备类型变化,重新初始化')\r\n this.$nextTick(() => {\r\n if (this.$refs.flipbook) {\r\n // 重置到第一页\r\n this.currentPage = 1\r\n this.startPage = 1\r\n }\r\n })\r\n }\r\n },\r\n flipLeft() {\r\n console.log('尝试向左翻页,当前页:', this.currentPage)\r\n if (this.flipMode === 'flip') {\r\n // 仿真翻页模式\r\n if (this.$refs.flipbook && this.currentPage > 1) {\r\n try {\r\n this.$refs.flipbook.flipLeft()\r\n console.log('向左翻页命令已发送')\r\n } catch (error) {\r\n console.error('向左翻页失败:', error)\r\n }\r\n } else {\r\n console.log('无法向左翻页 - 已在第一页或组件未就绪')\r\n }\r\n } else {\r\n // 其他模式直接切换页码\r\n if (this.currentPage > 1) {\r\n this.currentPage--\r\n this.onFlipLeftEnd(this.currentPage)\r\n }\r\n }\r\n },\r\n flipRight() {\r\n console.log('尝试向右翻页,当前页:', this.currentPage)\r\n if (this.flipMode === 'flip') {\r\n // 仿真翻页模式\r\n if (this.$refs.flipbook && this.currentPage < this.totalPages) {\r\n try {\r\n this.$refs.flipbook.flipRight()\r\n console.log('向右翻页命令已发送')\r\n } catch (error) {\r\n console.error('向右翻页失败:', error)\r\n }\r\n } else {\r\n console.log('无法向右翻页 - 已在最后一页或组件未就绪')\r\n }\r\n } else {\r\n // 其他模式直接切换页码\r\n if (this.currentPage < this.totalPages) {\r\n this.currentPage++\r\n this.onFlipRightEnd(this.currentPage)\r\n }\r\n }\r\n },\r\n onFlipLeftEnd(page) {\r\n console.log('向左翻页完成,新页面:', page)\r\n this.currentPage = page\r\n // 触发页码变化事件\r\n this.$emit('page-change', page)\r\n // 记录阅读进度\r\n this.recordReadingProgress(page)\r\n },\r\n onFlipRightEnd(page) {\r\n console.log('向右翻页完成,新页面:', page)\r\n this.currentPage = page\r\n // 触发页码变化事件\r\n this.$emit('page-change', page)\r\n // 记录阅读进度\r\n this.recordReadingProgress(page)\r\n },\r\n \r\n /**\r\n * 容器触摸开始事件\r\n */\r\n onContainerTouchStart(e) {\r\n this.touchStartTime = Date.now()\r\n this.initialTouches = Array.from(e.touches)\r\n \r\n if (e.touches.length === 2) {\r\n // 双指缩放\r\n this.isZooming = true\r\n this.lastTouchDistance = this.getTouchDistance(e.touches[0], e.touches[1])\r\n this.lastTouchCenter = this.getTouchCenter(e.touches[0], e.touches[1])\r\n e.preventDefault()\r\n } else if (e.touches.length === 1 && this.zoomScale > 1) {\r\n // 单指拖拽(仅在放大状态下)\r\n this.isPanning = true\r\n this.lastTouchCenter = { x: e.touches[0].clientX, y: e.touches[0].clientY }\r\n }\r\n },\r\n \r\n /**\r\n * 容器触摸移动事件\r\n */\r\n onContainerTouchMove(e) {\r\n if (this.isZooming && e.touches.length === 2) {\r\n // 双指缩放\r\n const currentDistance = this.getTouchDistance(e.touches[0], e.touches[1])\r\n const currentCenter = this.getTouchCenter(e.touches[0], e.touches[1])\r\n \r\n // 计算缩放比例\r\n const scaleChange = currentDistance / this.lastTouchDistance\r\n let newScale = this.zoomScale * scaleChange\r\n \r\n // 限制缩放范围\r\n newScale = Math.max(0.5, Math.min(3, newScale))\r\n \r\n // 计算缩放中心点的偏移\r\n const deltaX = currentCenter.x - this.lastTouchCenter.x\r\n const deltaY = currentCenter.y - this.lastTouchCenter.y\r\n \r\n this.zoomScale = newScale\r\n this.translateX += deltaX\r\n this.translateY += deltaY\r\n \r\n this.lastTouchDistance = currentDistance\r\n this.lastTouchCenter = currentCenter\r\n \r\n e.preventDefault()\r\n } else if (this.isPanning && e.touches.length === 1 && this.zoomScale > 1) {\r\n // 单指拖拽\r\n const deltaX = e.touches[0].clientX - this.lastTouchCenter.x\r\n const deltaY = e.touches[0].clientY - this.lastTouchCenter.y\r\n \r\n this.translateX += deltaX\r\n this.translateY += deltaY\r\n \r\n this.lastTouchCenter = { x: e.touches[0].clientX, y: e.touches[0].clientY }\r\n \r\n e.preventDefault()\r\n }\r\n },\r\n \r\n /**\r\n * 容器触摸结束事件\r\n */\r\n onContainerTouchEnd(e) {\r\n const touchDuration = Date.now() - this.touchStartTime\r\n const wasZooming = this.isZooming\r\n const wasPanning = this.isPanning\r\n\r\n // 先重置状态\r\n this.isZooming = false\r\n this.isPanning = false\r\n\r\n // 双击重置缩放 - 只在单指快速点击时检测\r\n if (e.changedTouches.length === 1 && touchDuration < 300 && !wasZooming && !wasPanning) {\r\n const now = Date.now()\r\n if (this.lastTapTime && now - this.lastTapTime < 400) {\r\n // 检测到双击\r\n this.resetZoom()\r\n console.log('移动端双击重置显示比例')\r\n this.lastTapTime = 0 // 重置,避免三击触发\r\n } else {\r\n this.lastTapTime = now\r\n }\r\n }\r\n\r\n // 边界检查和回弹\r\n this.constrainPosition()\r\n },\r\n \r\n /**\r\n * 获取两点间距离\r\n */\r\n getTouchDistance(touch1, touch2) {\r\n const dx = touch1.clientX - touch2.clientX\r\n const dy = touch1.clientY - touch2.clientY\r\n return Math.sqrt(dx * dx + dy * dy)\r\n },\r\n \r\n /**\r\n * 获取两点中心\r\n */\r\n getTouchCenter(touch1, touch2) {\r\n return {\r\n x: (touch1.clientX + touch2.clientX) / 2,\r\n y: (touch1.clientY + touch2.clientY) / 2\r\n }\r\n },\r\n \r\n /**\r\n * 重置缩放\r\n */\r\n resetZoom() {\r\n this.zoomScale = 1\r\n this.translateX = 0\r\n this.translateY = 0\r\n },\r\n\r\n /**\r\n * 放大\r\n */\r\n zoomIn() {\r\n this.zoomScale = Math.min(3, this.zoomScale + 0.2)\r\n },\r\n\r\n /**\r\n * 缩小\r\n */\r\n zoomOut() {\r\n this.zoomScale = Math.max(0.5, this.zoomScale - 0.2)\r\n },\r\n \r\n /**\r\n * PC端双击事件处理\r\n */\r\n onContainerDoubleClick(e) {\r\n // 阻止事件冒泡和默认行为,避免触发翻页\r\n e.preventDefault()\r\n e.stopPropagation()\r\n\r\n // 重置缩放比例\r\n this.resetZoom()\r\n console.log('容器双击重置显示比例,当前缩放:', this.zoomScale)\r\n },\r\n\r\n /**\r\n * Flipbook区域双击事件处理\r\n */\r\n onFlipbookDoubleClick(e) {\r\n // 阻止事件冒泡和默认行为,避免触发翻页\r\n e.preventDefault()\r\n e.stopPropagation()\r\n\r\n // 重置缩放比例\r\n this.resetZoom()\r\n console.log('Flipbook双击重置显示比例,当前缩放:', this.zoomScale)\r\n },\r\n\r\n /**\r\n * Flipbook区域触摸开始事件\r\n */\r\n onFlipbookTouchStart(e) {\r\n this.flipbookTouchStartTime = Date.now()\r\n },\r\n\r\n /**\r\n * Flipbook区域触摸结束事件(移动端双击检测)\r\n */\r\n onFlipbookTouchEnd(e) {\r\n const touchDuration = Date.now() - this.flipbookTouchStartTime\r\n\r\n // 双击重置缩放 - 只在单指快速点击时检测\r\n if (e.changedTouches.length === 1 && touchDuration < 300) {\r\n const now = Date.now()\r\n if (this.flipbookLastTapTime && now - this.flipbookLastTapTime < 400) {\r\n // 检测到双击\r\n e.preventDefault()\r\n e.stopPropagation()\r\n this.resetZoom()\r\n console.log('Flipbook区域移动端双击重置显示比例')\r\n this.flipbookLastTapTime = 0 // 重置,避免三击触发\r\n } else {\r\n this.flipbookLastTapTime = now\r\n }\r\n }\r\n },\r\n \r\n /**\r\n * 约束位置在边界内\r\n */\r\n constrainPosition() {\r\n if (this.zoomScale <= 1) {\r\n this.translateX = 0\r\n this.translateY = 0\r\n return\r\n }\r\n \r\n const maxTranslate = (this.zoomScale - 1) * 200\r\n this.translateX = Math.max(-maxTranslate, Math.min(maxTranslate, this.translateX))\r\n this.translateY = Math.max(-maxTranslate, Math.min(maxTranslate, this.translateY))\r\n },\r\n\r\n\r\n /**\r\n * 打开目录抽屉\r\n */\r\n openCatalogue() {\r\n console.log('点击目录按钮', {\r\n dragging: this.catalogueButtonDragging,\r\n showDrawer: this.showCatalogueDrawer\r\n })\r\n \r\n // 只有在不是拖拽状态下才打开目录\r\n if (!this.catalogueButtonDragging) {\r\n this.showCatalogueDrawer = true\r\n console.log('目录抽屉已打开')\r\n } else {\r\n console.log('拖拽状态中,不打开目录')\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮拖拽开始\r\n */\r\n onCatalogueMouseDown(e) {\r\n // 不立即设置为拖拽状态,等待移动再判断\r\n this.catalogueButtonDragging = false\r\n \r\n const rect = e.target.closest('.catalogue-button').getBoundingClientRect()\r\n this.dragStartPosition = {\r\n x: e.clientX,\r\n y: e.clientY\r\n }\r\n this.dragOffset = {\r\n x: e.clientX - rect.left,\r\n y: e.clientY - rect.top\r\n }\r\n \r\n document.addEventListener('mousemove', this.onCatalogueMouseMove)\r\n document.addEventListener('mouseup', this.onCatalogueMouseUp)\r\n },\r\n\r\n /**\r\n * 目录按钮拖拽移动\r\n */\r\n onCatalogueMouseMove(e) {\r\n // 检查是否开始拖拽\r\n const dragDistance = Math.sqrt(\r\n Math.pow(e.clientX - this.dragStartPosition.x, 2) + \r\n Math.pow(e.clientY - this.dragStartPosition.y, 2)\r\n )\r\n \r\n // 只有移动距离超过5px才认为是拖拽\r\n if (dragDistance > 5) {\r\n this.catalogueButtonDragging = true\r\n }\r\n \r\n if (!this.catalogueButtonDragging) return\r\n \r\n e.preventDefault()\r\n \r\n const newX = e.clientX - this.dragOffset.x\r\n const newY = e.clientY - this.dragOffset.y\r\n \r\n // 限制在视窗范围内\r\n const maxX = window.innerWidth - 120 // 按钮宽度约120px\r\n const maxY = window.innerHeight - 50 // 按钮高度约50px\r\n \r\n this.catalogueButtonPosition = {\r\n x: Math.max(0, Math.min(maxX, newX)),\r\n y: Math.max(0, Math.min(maxY, newY))\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮拖拽结束\r\n */\r\n onCatalogueMouseUp(e) {\r\n // 立即重置拖拽状态\r\n this.catalogueButtonDragging = false\r\n \r\n document.removeEventListener('mousemove', this.onCatalogueMouseMove)\r\n document.removeEventListener('mouseup', this.onCatalogueMouseUp)\r\n },\r\n\r\n /**\r\n * 目录按钮触摸开始(移动端)\r\n */\r\n onCatalogueTouchStart(e) {\r\n // 不立即设置为拖拽状态\r\n this.catalogueButtonDragging = false\r\n \r\n const touch = e.touches[0]\r\n const rect = e.target.closest('.catalogue-button').getBoundingClientRect()\r\n \r\n this.dragStartPosition = {\r\n x: touch.clientX,\r\n y: touch.clientY\r\n }\r\n this.dragOffset = {\r\n x: touch.clientX - rect.left,\r\n y: touch.clientY - rect.top\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮触摸移动(移动端)\r\n */\r\n onCatalogueTouchMove(e) {\r\n const touch = e.touches[0]\r\n \r\n // 检查是否开始拖拽\r\n const dragDistance = Math.sqrt(\r\n Math.pow(touch.clientX - this.dragStartPosition.x, 2) + \r\n Math.pow(touch.clientY - this.dragStartPosition.y, 2)\r\n )\r\n \r\n // 只有移动距离超过5px才认为是拖拽\r\n if (dragDistance > 5) {\r\n this.catalogueButtonDragging = true\r\n }\r\n \r\n if (!this.catalogueButtonDragging) return\r\n \r\n e.preventDefault()\r\n \r\n const newX = touch.clientX - this.dragOffset.x\r\n const newY = touch.clientY - this.dragOffset.y\r\n \r\n // 限制在视窗范围内,考虑安全区域\r\n const maxX = window.innerWidth - 120\r\n const maxY = window.innerHeight - 80\r\n \r\n this.catalogueButtonPosition = {\r\n x: Math.max(15, Math.min(maxX, newX)),\r\n y: Math.max(15, Math.min(maxY, newY))\r\n }\r\n },\r\n\r\n /**\r\n * 目录按钮触摸结束(移动端)\r\n */\r\n onCatalogueTouchEnd(e) {\r\n // 立即重置拖拽状态\r\n this.catalogueButtonDragging = false\r\n },\r\n \r\n /**\r\n * 切换翻页模式菜单显示\r\n */\r\n toggleFlipModeMenu() {\r\n this.showFlipModeMenu = !this.showFlipModeMenu\r\n },\r\n \r\n /**\r\n * 选择翻页模式\r\n * @param {string} mode - 翻页模式\r\n */\r\n selectFlipMode(mode) {\r\n const oldMode = this.flipMode\r\n this.flipMode = mode\r\n this.showFlipModeMenu = false\r\n \r\n // 保存翻页模式到本地存储\r\n localStorage.setItem('book-viewer-flip-mode', mode)\r\n \r\n console.log('切换翻页模式:', { from: oldMode, to: mode })\r\n \r\n // 如果切换到滚动模式,需要滚动到当前页\r\n if (mode === 'scroll') {\r\n this.$nextTick(() => {\r\n this.scrollToCurrentPage()\r\n })\r\n }\r\n },\r\n \r\n /**\r\n * 滑动模式触摸开始\r\n */\r\n onSlideViewerTouchStart(e) {\r\n if (e.touches.length === 1) {\r\n this.slideStartX = e.touches[0].clientX\r\n this.slideCurrentX = e.touches[0].clientX\r\n this.slideIsDragging = true\r\n }\r\n },\r\n \r\n /**\r\n * 滑动模式触摸移动\r\n */\r\n onSlideViewerTouchMove(e) {\r\n if (this.slideIsDragging && e.touches.length === 1) {\r\n this.slideCurrentX = e.touches[0].clientX\r\n e.preventDefault()\r\n }\r\n },\r\n \r\n /**\r\n * 滑动模式触摸结束\r\n */\r\n onSlideViewerTouchEnd(e) {\r\n if (this.slideIsDragging) {\r\n const deltaX = this.slideCurrentX - this.slideStartX\r\n const threshold = 50 // 滑动阈值\r\n \r\n if (deltaX > threshold && this.currentPage > 1) {\r\n // 向右滑动,上一页\r\n this.currentPage--\r\n this.recordReadingProgress(this.currentPage)\r\n } else if (deltaX < -threshold && this.currentPage < this.totalPages) {\r\n // 向左滑动,下一页\r\n this.currentPage++\r\n this.recordReadingProgress(this.currentPage)\r\n }\r\n \r\n this.slideIsDragging = false\r\n }\r\n },\r\n \r\n /**\r\n * 滚动模式滚动事件\r\n */\r\n onScrollViewerScroll(e) {\r\n const container = this.$refs.scrollViewer\r\n if (!container) return\r\n \r\n const scrollTop = container.scrollTop\r\n const containerHeight = container.clientHeight\r\n \r\n // 计算当前页码\r\n for (let i = 0; i < this.pages.length; i++) {\r\n const pageRef = this.$refs['scrollPage' + i]\r\n if (pageRef && pageRef[0]) {\r\n const pageTop = pageRef[0].offsetTop\r\n const pageBottom = pageTop + pageRef[0].offsetHeight\r\n \r\n if (scrollTop >= pageTop - containerHeight / 2 && scrollTop < pageBottom - containerHeight / 2) {\r\n if (this.currentPage !== i + 1) {\r\n this.currentPage = i + 1\r\n this.recordReadingProgress(this.currentPage)\r\n }\r\n break\r\n }\r\n }\r\n }\r\n },\r\n \r\n /**\r\n * 滚动到当前页\r\n */\r\n scrollToCurrentPage() {\r\n if (this.flipMode !== 'scroll') return\r\n \r\n const pageRef = this.$refs['scrollPage' + (this.currentPage - 1)]\r\n if (pageRef && pageRef[0]) {\r\n pageRef[0].scrollIntoView({ behavior: 'smooth', block: 'start' })\r\n }\r\n },\r\n \r\n /**\r\n * 关闭翻页模式菜单(点击外部)\r\n */\r\n closeFlipModeMenu(e) {\r\n if (!e.target.closest('.flip-mode-selector')) {\r\n this.showFlipModeMenu = false\r\n }\r\n },\r\n \r\n /**\r\n * 获取卡片样式\r\n * @param {string} position - 卡片位置:prev, current, next\r\n */\r\n getCardStyle(position) {\r\n switch (position) {\r\n case 'prev':\r\n return {\r\n transform: 'translateX(-70%) scale(0.85) rotateY(15deg)',\r\n zIndex: 1,\r\n opacity: 0.6,\r\n filter: 'brightness(0.8)'\r\n }\r\n case 'current':\r\n return {\r\n transform: 'translateX(0) scale(1) rotateY(0deg)',\r\n zIndex: 3,\r\n opacity: 1,\r\n filter: 'brightness(1)'\r\n }\r\n case 'next':\r\n return {\r\n transform: 'translateX(70%) scale(0.85) rotateY(-15deg)',\r\n zIndex: 1,\r\n opacity: 0.6,\r\n filter: 'brightness(0.8)'\r\n }\r\n default:\r\n return {}\r\n }\r\n },\r\n\r\n /**\r\n * 目录项点击事件\r\n * @param {Object} item - 点击的目录项\r\n */\r\n onCatalogueClick(item) {\r\n console.log('目录项被点击:', item)\r\n this.$emit('catalogue-click', item)\r\n },\r\n\r\n /**\r\n * 获取目录数据事件\r\n * @param {String} bookId - 书籍ID\r\n */\r\n onFetchCatalogue(bookId) {\r\n this.$emit('fetch-catalogue', bookId)\r\n },\r\n\r\n /**\r\n * 页面跳转事件\r\n * @param {number} pageNumber - 目标页码\r\n */\r\n onFocus() {\r\n // 使用flipbook的方法跳转到指定页面\r\n if (this.flipMode === 'flip') {\r\n this.flag = false\r\n setTimeout(() => {\r\n this.flag = true\r\n }, 500)\r\n } else if (this.flipMode === 'scroll') {\r\n this.scrollToCurrentPage()\r\n }\r\n },\r\n onPageJump(pageNumber) {\r\n console.log('跳转到页面:', pageNumber)\r\n\r\n // 关闭目录抽屉\r\n this.showCatalogueDrawer = false\r\n\r\n // 跳转到指定页面\r\n if (pageNumber && pageNumber >= 1 && pageNumber <= this.totalPages) {\r\n this.currentPage = pageNumber\r\n this.startPage = pageNumber\r\n\r\n // 记录阅读进度\r\n this.recordReadingProgress(pageNumber)\r\n\r\n // 使用flipbook的方法跳转到指定页面\r\n if (this.flipMode === 'flip') {\r\n this.flag = false\r\n setTimeout(() => {\r\n this.flag = true\r\n this.$nextTick(() => {\r\n this.$refs.flipbook.goToPage(pageNumber)\r\n })\r\n }, 500)\r\n } else if (this.flipMode === 'scroll') {\r\n this.scrollToCurrentPage()\r\n }\r\n } else {\r\n console.warn('无效的页码:', pageNumber)\r\n }\r\n },\r\n\r\n /**\r\n * 检查URL参数并跳转到指定页码\r\n */\r\n checkUrlPageParameter() {\r\n // 从URL查询参数中获取页码\r\n const pageParam = this.$route.query.page\r\n \r\n if (pageParam) {\r\n const targetPage = parseInt(pageParam, 10)\r\n \r\n console.log('检测到URL页码参数:', {\r\n pageParam,\r\n targetPage,\r\n totalPages: this.totalPages\r\n })\r\n \r\n // 验证页码是否有效\r\n if (!isNaN(targetPage) && targetPage >= 1 && targetPage <= this.totalPages) {\r\n console.log('准备跳转到页码:', targetPage)\r\n \r\n // 使用nextTick确保DOM已更新\r\n this.$nextTick(() => {\r\n this.jumpToPage(targetPage)\r\n })\r\n } else {\r\n console.warn('无效的页码参数:', {\r\n pageParam,\r\n targetPage,\r\n totalPages: this.totalPages,\r\n isValid: !isNaN(targetPage) && targetPage >= 1 && targetPage <= this.totalPages\r\n })\r\n }\r\n } else {\r\n console.log('URL中未检测到页码参数')\r\n }\r\n },\r\n\r\n /**\r\n * 跳转到指定页码\r\n * @param {number} pageNumber - 目标页码\r\n */\r\n jumpToPage(pageNumber) {\r\n if (!pageNumber || pageNumber < 1 || pageNumber > this.totalPages) {\r\n console.warn('无效的页码:', pageNumber)\r\n return\r\n }\r\n\r\n console.log('跳转到页面:', pageNumber)\r\n \r\n // 更新当前页码和起始页码\r\n this.currentPage = pageNumber\r\n this.startPage = pageNumber\r\n \r\n // 记录阅读进度\r\n this.recordReadingProgress(pageNumber)\r\n \r\n // 使用flipbook的方法跳转到指定页面\r\n if (this.flipMode === 'flip') {\r\n if (this.$refs.flipbook && this.$refs.flipbook.goToPage) {\r\n try {\r\n this.$refs.flipbook.goToPage(pageNumber)\r\n console.log('页面跳转成功:', pageNumber)\r\n } catch (error) {\r\n console.error('页面跳转失败:', error)\r\n }\r\n } else {\r\n console.warn('Flipbook组件未就绪,无法跳转页面')\r\n }\r\n } else if (this.flipMode === 'scroll') {\r\n this.scrollToCurrentPage()\r\n }\r\n // slide 和 fade 模式直接通过 currentPage 变化来切换\r\n },\r\n\r\n /**\r\n * 记录阅读进度\r\n * @param {number} pageNumber - 当前页码\r\n */\r\n async recordReadingProgress(pageNumber) {\r\n if (!this.bookId || !pageNumber) {\r\n console.warn('缺少书籍ID或页码,无法记录阅读进度')\r\n return\r\n }\r\n\r\n try {\r\n console.log('记录阅读进度:', {\r\n bookId: this.bookId,\r\n pageNumber: pageNumber\r\n })\r\n\r\n // 注释:原来调用API记录进度,现在改为触发事件让父组件处理\r\n // await booksApi.recordReadProgress(this.bookId, pageNumber)\r\n this.$emit('record-progress', { bookId: this.bookId, pageNumber })\r\n\r\n console.log('阅读进度记录成功')\r\n\r\n } catch (error) {\r\n console.error('记录阅读进度失败:', error)\r\n // 阅读记录失败不影响用户体验,只记录日志\r\n }\r\n },\r\n\r\n /**\r\n * 显示控件\r\n */\r\n showControlsTemporarily() {\r\n this.showControls = true\r\n\r\n // 清除之前的定时器\r\n if (this.hideControlsTimer) {\r\n clearTimeout(this.hideControlsTimer)\r\n }\r\n\r\n // 3秒后自动隐藏\r\n this.hideControlsTimer = setTimeout(() => {\r\n this.showControls = false\r\n }, 3000)\r\n },\r\n\r\n /**\r\n * 内容区域点击事件\r\n */\r\n onContentClick() {\r\n this.showControlsTemporarily()\r\n },\r\n\r\n /**\r\n * 处理页码输入\r\n */\r\n handlePageInput(e) {\r\n const value = e.target.value\r\n // 只允许输入数字\r\n const numValue = value.replace(/[^\\d]/g, '')\r\n // 限制不超过总页数\r\n if (numValue && parseInt(numValue) > this.totalPages) {\r\n this.jumpPageInput = this.totalPages.toString()\r\n } else {\r\n this.jumpPageInput = numValue\r\n }\r\n },\r\n\r\n /**\r\n * 执行页码跳转\r\n */\r\n handleJumpToPage() {\r\n const pageNum = parseInt(this.jumpPageInput)\r\n if (pageNum && pageNum >= 1 && pageNum <= this.totalPages) {\r\n // 重置缩放和位移,避免内容区域缩小\r\n this.resetZoom()\r\n this.jumpToPage(pageNum)\r\n this.jumpPageInput = ''\r\n }\r\n }\r\n },\r\n watch: {\r\n async pages(newPages) {\r\n if (newPages && newPages.length > 0 && this.isMobile) {\r\n this.contentReady = false\r\n await this.$nextTick()\r\n await this.preloadFirstPage()\r\n } else if (newPages && newPages.length > 0) {\r\n this.contentReady = true\r\n }\r\n }\r\n },\r\n async mounted() {\r\n console.log('组件已挂载,总页数:', this.totalPages)\r\n\r\n // 检测设备类型\r\n this.detectMobile()\r\n\r\n // 检查URL参数中的翻页模式\r\n const urlFlipMode = this.$route.query.mode || this.$route.query.flipMode\r\n if (urlFlipMode && this.flipModes.some(m => m.value === urlFlipMode)) {\r\n // URL参数指定的翻页模式优先级最高,并隐藏切换按钮\r\n this.flipMode = urlFlipMode\r\n this.flipModeFromUrl = true\r\n console.log('从URL参数读取翻页模式:', urlFlipMode)\r\n } else {\r\n // 否则从本地存储恢复\r\n const savedFlipMode = localStorage.getItem('book-viewer-flip-mode')\r\n if (savedFlipMode && this.flipModes.some(m => m.value === savedFlipMode)) {\r\n this.flipMode = savedFlipMode\r\n }\r\n }\r\n\r\n // 预加载首页图片(移动端优化)\r\n if (this.isMobile && this.pages && this.pages.length > 0) {\r\n await this.$nextTick()\r\n await this.preloadFirstPage()\r\n } else if (this.pages && this.pages.length > 0) {\r\n this.contentReady = true\r\n }\r\n\r\n // 监听窗口尺寸变化\r\n window.addEventListener('resize', this.handleResize)\r\n\r\n // 监听点击事件,用于关闭翻页模式菜单\r\n document.addEventListener('click', this.closeFlipModeMenu)\r\n },\r\n \r\n beforeDestroy() {\r\n // 清理事件监听\r\n window.removeEventListener('resize', this.handleResize)\r\n // 清理拖拽事件监听\r\n document.removeEventListener('mousemove', this.onCatalogueMouseMove)\r\n document.removeEventListener('mouseup', this.onCatalogueMouseUp)\r\n // 清理翻页模式菜单事件监听\r\n document.removeEventListener('click', this.closeFlipModeMenu)\r\n // 清理控件自动隐藏定时器\r\n if (this.hideControlsTimer) {\r\n clearTimeout(this.hideControlsTimer)\r\n }\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped>\r\n.photo-album-container {\r\n /* min-height: 100vh; */\r\n background: transparent;\r\n padding: 5px 5px 80px 5px;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n transform-origin: center center;\r\n transition: transform 0.2s ease-out;\r\n overflow: hidden;\r\n position: relative;\r\n}\r\n\r\n.album-header {\r\n text-align: center;\r\n color: #495057;\r\n margin-bottom: 30px;\r\n}\r\n\r\n.album-header h1 {\r\n font-size: 2.5rem;\r\n margin-bottom: 10px;\r\n text-shadow: 1px 1px 2px rgba(0,0,0,0.1);\r\n font-weight: 600;\r\n}\r\n\r\n.album-header p {\r\n font-size: 1.1rem;\r\n opacity: 0.9;\r\n}\r\n\r\n.flipbook-wrapper {\r\n perspective: 2000px;\r\n margin-bottom: 20px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n width: 100%;\r\n}\r\n\r\n.flipbook {\r\n width: 1600px;\r\n height: 1200px;\r\n box-shadow: none;\r\n border-radius: 0;\r\n overflow: hidden;\r\n transition: all 0.3s ease;\r\n}\r\n\r\n/* 隐藏标题时的调整 */\r\n.album-header[style*=\"display: none\"],\r\n.album-header[v-if=\"false\"] {\r\n display: none !important;\r\n}\r\n\r\n.photo-album-container:has(.album-header[v-if=\"false\"]) .flipbook,\r\n.photo-album-container .flipbook {\r\n max-height: calc(100vh - 100px);\r\n}\r\n\r\n/* 移动端单页模式样式 */\r\n.flipbook.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 92vh;\r\n max-height: 92vh;\r\n margin: 0 auto;\r\n}\r\n\r\n/* PC端双页模式样式 */\r\n.flipbook.desktop-mode {\r\n width: 1600px;\r\n height: 1200px;\r\n margin: 0 auto;\r\n}\r\n\r\n.page {\r\n background: white;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n position: relative;\r\n border-radius: 5px;\r\n}\r\n\r\n.page--cover {\r\n background: linear-gradient(45deg, #e9ecef 0%, #dee2e6 50%, #ced4da 100%);\r\n color: #495057;\r\n}\r\n\r\n.cover-page, .back-cover {\r\n text-align: center;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n padding: 40px;\r\n}\r\n\r\n.cover-page h2 {\r\n font-size: 3rem;\r\n margin-bottom: 20px;\r\n text-shadow: 2px 2px 4px rgba(0,0,0,0.3);\r\n font-weight: bold;\r\n}\r\n\r\n.cover-page p {\r\n font-size: 1.5rem;\r\n opacity: 0.9;\r\n margin-bottom: 30px;\r\n}\r\n\r\n.cover-decoration {\r\n display: flex;\r\n align-items: center;\r\n gap: 15px;\r\n margin-top: 20px;\r\n}\r\n\r\n.decoration-line {\r\n width: 60px;\r\n height: 2px;\r\n background: rgba(73,80,87,0.6);\r\n border-radius: 1px;\r\n}\r\n\r\n.cover-decoration span {\r\n font-size: 1.1rem;\r\n opacity: 0.9;\r\n white-space: nowrap;\r\n}\r\n\r\n.back-cover h3 {\r\n font-size: 2.5rem;\r\n margin-bottom: 15px;\r\n text-shadow: 2px 2px 4px rgba(0,0,0,0.3);\r\n}\r\n\r\n.back-cover p {\r\n font-size: 1.2rem;\r\n opacity: 0.8;\r\n margin-bottom: 30px;\r\n}\r\n\r\n.back-decoration p {\r\n font-size: 1.5rem;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.back-decoration small {\r\n opacity: 0.7;\r\n font-size: 1rem;\r\n}\r\n\r\n.content-page {\r\n padding: 20px;\r\n height: 100%;\r\n width: 100%;\r\n box-sizing: border-box;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n}\r\n\r\n.image-container {\r\n flex: 1;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n margin-bottom: 0;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n}\r\n\r\n.image-container img {\r\n max-width: 100%;\r\n max-height: 100%;\r\n width: auto;\r\n height: auto;\r\n object-fit: contain;\r\n border-radius: 8px;\r\n box-shadow: 0 4px 12px rgba(0,0,0,0.1);\r\n transition: transform 0.3s ease;\r\n}\r\n\r\n.image-container img:hover {\r\n transform: scale(1.02);\r\n}\r\n\r\n.page-content-info {\r\n background: rgba(255,255,255,0.95);\r\n padding: 8px;\r\n border-radius: 12px;\r\n box-shadow: 0 2px 8px rgba(0,0,0,0.1);\r\n backdrop-filter: blur(10px);\r\n margin-top: 4px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.image-title {\r\n font-size: 1.2rem;\r\n font-weight: bold;\r\n color: #2c3e50;\r\n margin-bottom: 8px;\r\n text-align: center;\r\n display: block;\r\n}\r\n\r\n.image-description {\r\n font-size: 0.9rem;\r\n color: #5a6c7d;\r\n line-height: 1.4;\r\n text-align: center;\r\n margin-bottom: 10px;\r\n display: block;\r\n}\r\n\r\n.page-number {\r\n text-align: center;\r\n padding-top: 10px;\r\n border-top: 1px solid rgba(0,0,0,0.1);\r\n}\r\n\r\n.page-number span {\r\n background: linear-gradient(45deg, #667eea, #764ba2);\r\n color: white;\r\n padding: 4px 12px;\r\n border-radius: 12px;\r\n font-size: 0.8rem;\r\n font-weight: bold;\r\n}\r\n\r\n.controls {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n background: transparent;\r\n padding: 12px 20px;\r\n border-radius: 0;\r\n backdrop-filter: none;\r\n box-shadow: none;\r\n border: none;\r\n width: fit-content;\r\n margin: 20px auto;\r\n z-index: 1000;\r\n}\r\n\r\n.desktop-controls {\r\n position: relative;\r\n margin: 20px auto;\r\n}\r\n\r\n.mobile-controls {\r\n position: fixed;\r\n bottom: 20px;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n margin: 0;\r\n max-width: 90vw;\r\n}\r\n\r\n.btn {\r\n background: #333;\r\n color: white;\r\n border: 1px solid #666;\r\n padding: 8px 16px;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-size: 0.9rem;\r\n transition: all 0.3s ease;\r\n box-shadow: none;\r\n}\r\n\r\n.btn:hover:not(:disabled) {\r\n background: #555;\r\n transform: none;\r\n box-shadow: none;\r\n}\r\n\r\n.btn:disabled {\r\n opacity: 0.5;\r\n cursor: not-allowed;\r\n transform: none;\r\n}\r\n\r\n.page-indicator {\r\n color: #495057;\r\n font-size: 0.95rem;\r\n font-weight: bold;\r\n text-shadow: none;\r\n min-width: 70px;\r\n text-align: center;\r\n}\r\n\r\n/* 页码跳转容器样式 */\r\n.page-jump-container {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n background: rgba(255, 255, 255, 0.95);\r\n padding: 10px 16px;\r\n border-radius: 8px;\r\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);\r\n z-index: 1000;\r\n width: fit-content;\r\n margin: 10px auto 0;\r\n}\r\n\r\n.mobile-page-jump {\r\n padding: 8px 12px;\r\n margin: 10px auto 0;\r\n}\r\n\r\n.page-jump-input {\r\n width: 120px;\r\n padding: 8px 12px;\r\n border: 1px solid #ddd;\r\n border-radius: 6px;\r\n background: #fff;\r\n color: #333;\r\n font-size: 16px;\r\n text-align: center;\r\n outline: none;\r\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\r\n}\r\n\r\n.page-jump-input:focus {\r\n border-color: #333;\r\n box-shadow: 0 0 0 2px rgba(51, 51, 51, 0.1);\r\n}\r\n\r\n.page-jump-input::placeholder {\r\n color: #999;\r\n font-size: 0.85rem;\r\n}\r\n\r\n.btn-jump {\r\n padding: 8px 16px;\r\n white-space: nowrap;\r\n background: #333;\r\n color: white;\r\n border: none;\r\n border-radius: 6px;\r\n font-size: 0.9rem;\r\n cursor: pointer;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.btn-jump:hover {\r\n background: #555;\r\n}\r\n\r\n/* PC端响应式设计 */\r\n@media (max-width: 1800px) and (min-width: 769px) {\r\n .flipbook.desktop-mode {\r\n width: 95vw;\r\n height: calc(95vw * 0.625);\r\n max-width: 1600px;\r\n max-height: 1000px;\r\n }\r\n}\r\n\r\n/* 移动端样式 */\r\n@media (max-width: 768px) {\r\n .photo-album-container {\r\n padding: 5px 5px 120px 5px;\r\n align-items: center;\r\n touch-action: none;\r\n overflow: hidden;\r\n }\r\n\r\n .flipbook-wrapper {\r\n width: 100%;\r\n display: flex;\r\n justify-content: center;\r\n }\r\n\r\n .flipbook.mobile-mode {\r\n width: 98vw;\r\n height: 85vh;\r\n max-width: none;\r\n max-height: 85vh;\r\n margin: 0 auto;\r\n }\r\n\r\n .album-header {\r\n width: 100%;\r\n text-align: center;\r\n }\r\n\r\n .album-header h1 {\r\n font-size: 2rem;\r\n }\r\n\r\n\r\n .btn {\r\n padding: 8px 16px;\r\n font-size: 0.9rem;\r\n }\r\n\r\n .page-indicator {\r\n font-size: 0.85rem;\r\n min-width: 120px;\r\n text-align: center;\r\n }\r\n\r\n /* 移动端页码跳转 */\r\n .page-jump-container {\r\n top: 10px;\r\n right: 10px;\r\n padding: 8px 10px;\r\n gap: 6px;\r\n }\r\n\r\n .page-jump-input {\r\n width: 100px;\r\n padding: 6px 10px;\r\n font-size: 0.85rem;\r\n }\r\n\r\n .btn-jump {\r\n padding: 6px 12px;\r\n font-size: 0.85rem;\r\n }\r\n\r\n /* 移动端图片优化 */\r\n .image-container {\r\n margin-bottom: 0;\r\n }\r\n\r\n .image-container img {\r\n max-width: 100%;\r\n max-height: 100%;\r\n border-radius: 6px;\r\n }\r\n\r\n .page-content-info {\r\n padding: 6px;\r\n margin-top: 2px;\r\n }\r\n}\r\n\r\n@media (max-width: 480px) {\r\n .flipbook.mobile-mode {\r\n width: 98vw;\r\n height: 80vh;\r\n max-height: 80vh;\r\n margin: 0 auto;\r\n }\r\n \r\n .album-header h1 {\r\n font-size: 1.8rem;\r\n }\r\n \r\n .mobile-controls {\r\n gap: 6px;\r\n padding: 8px 12px;\r\n justify-content: center;\r\n align-items: center;\r\n bottom: 15px;\r\n width: 95%;\r\n /* max-width: 95vw; */\r\n }\r\n \r\n .btn {\r\n padding: 6px 12px;\r\n font-size: 0.8rem;\r\n }\r\n \r\n .page-indicator {\r\n text-align: center;\r\n min-width: 100px;\r\n }\r\n \r\n /* 小屏幕图片优化 */\r\n .image-container {\r\n margin-bottom: 0;\r\n }\r\n \r\n .image-container img {\r\n max-width: 100%;\r\n max-height: 100%;\r\n border-radius: 4px;\r\n }\r\n \r\n .page-content-info {\r\n padding: 4px;\r\n margin-top: 1px;\r\n }\r\n}\r\n\r\n/* 设备特定的优化 */\r\n.mobile-optimized {\r\n /* 移动端优化 */\r\n -webkit-tap-highlight-color: transparent;\r\n -webkit-touch-callout: none;\r\n -webkit-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n.desktop-optimized {\r\n /* PC端优化 */\r\n cursor: pointer;\r\n}\r\n\r\n/* 翻页按钮在不同设备上的样式 */\r\n.btn {\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.btn:hover:not(:disabled) {\r\n background: #555;\r\n transform: none;\r\n box-shadow: none;\r\n}\r\n\r\n/* 缩放提示样式 */\r\n.zoom-hint {\r\n position: fixed;\r\n top: 20px;\r\n right: 20px;\r\n background: rgba(0, 0, 0, 0.85);\r\n color: white;\r\n padding: 10px 16px;\r\n border-radius: 25px;\r\n font-size: 0.85rem;\r\n opacity: 0;\r\n transform: translateY(-10px);\r\n transition: all 0.3s ease;\r\n z-index: 10001;\r\n pointer-events: none;\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.zoom-hint.show {\r\n opacity: 1;\r\n transform: translateY(0);\r\n pointer-events: auto;\r\n}\r\n\r\n.zoom-info {\r\n display: flex;\r\n flex-direction: row;\r\n align-items: center;\r\n gap: 12px;\r\n}\r\n\r\n.zoom-info span {\r\n font-weight: 500;\r\n}\r\n\r\n.btn-reset-zoom {\r\n background: rgba(255, 255, 255, 0.2);\r\n color: white;\r\n border: 1px solid rgba(255, 255, 255, 0.3);\r\n padding: 4px 12px;\r\n border-radius: 15px;\r\n font-size: 0.75rem;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n font-weight: 600;\r\n white-space: nowrap;\r\n}\r\n\r\n.btn-reset-zoom:hover {\r\n background: rgba(255, 255, 255, 0.3);\r\n border-color: rgba(255, 255, 255, 0.5);\r\n transform: scale(1.05);\r\n}\r\n\r\n.btn-reset-zoom:active {\r\n transform: scale(0.95);\r\n}\r\n\r\n/* 控件显示/隐藏动画 */\r\n.controls {\r\n opacity: 0;\r\n visibility: hidden;\r\n transform: translateY(20px);\r\n transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;\r\n display: flex;\r\n background: rgba(255, 255, 255, 0.9);\r\n border-radius: 8px;\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.controls.controls-visible {\r\n opacity: 1;\r\n visibility: visible;\r\n transform: translateY(0);\r\n}\r\n\r\n.mobile-controls {\r\n position: fixed !important;\r\n bottom: 76px !important;\r\n left: 50% !important;\r\n transform: translateX(-50%) !important;\r\n z-index: 10000 !important;\r\n}\r\n\r\n.desktop-controls {\r\n position: relative !important;\r\n margin: 20px auto !important;\r\n}\r\n\r\n/* 目录按钮样式 */\r\n.catalogue-button {\r\n position: fixed;\r\n z-index: 9999999999;\r\n opacity: 0;\r\n visibility: hidden;\r\n transform: translateX(-20px);\r\n transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;\r\n user-select: none;\r\n}\r\n\r\n.catalogue-button.catalogue-visible {\r\n opacity: 1;\r\n visibility: visible;\r\n transform: translateX(0);\r\n}\r\n\r\n.catalogue-button.dragging {\r\n transform: scale(1.05);\r\n /* box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); */\r\n z-index: 999999;\r\n}\r\n\r\n\r\n.btn-catalogue {\r\n background: linear-gradient(45deg, #1989fa, #0066cc);\r\n color: white;\r\n border: 2px solid rgba(255, 255, 255, 0.3);\r\n padding: 10px 16px;\r\n border-radius: 20px;\r\n font-size: 0.9rem;\r\n font-weight: 600;\r\n box-shadow: 0 4px 12px rgba(25, 137, 250, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);\r\n transition: all 0.2s ease;\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n cursor: grab;\r\n white-space: nowrap;\r\n pointer-events: all;\r\n}\r\n\r\n.catalogue-button.dragging .btn-catalogue {\r\n cursor: grabbing;\r\n /* background: linear-gradient(45deg, #0066cc, #004499); */\r\n}\r\n\r\n.btn-catalogue:active {\r\n transform: scale(0.98);\r\n}\r\n\r\n/* 拖拽手柄样式 */\r\n.btn-catalogue .drag-handle {\r\n color: rgba(255, 255, 255, 0.7);\r\n font-size: 14px;\r\n margin-right: 2px;\r\n user-select: none;\r\n}\r\n\r\n.catalogue-icon {\r\n font-size: 1rem;\r\n flex-shrink: 0;\r\n}\r\n\r\n.catalogue-text {\r\n font-size: 0.9rem;\r\n flex-shrink: 0;\r\n}\r\n\r\n.btn-catalogue:hover {\r\n background: linear-gradient(45deg, #0066cc, #004499);\r\n transform: translateY(-2px);\r\n box-shadow: 0 6px 16px rgba(25, 137, 250, 0.4);\r\n}\r\n\r\n.btn-catalogue:hover .drag-handle {\r\n color: rgba(255, 255, 255, 0.9);\r\n}\r\n\r\n/* PC端缩放工具栏 */\r\n.zoom-toolbar {\r\n position: fixed;\r\n bottom: 20px;\r\n right: 20px;\r\n display: flex;\r\n gap: 8px;\r\n z-index: 10000;\r\n}\r\n\r\n.btn-zoom {\r\n width: 40px;\r\n height: 40px;\r\n background: #333;\r\n color: white;\r\n border: 1px solid #666;\r\n border-radius: 4px;\r\n font-size: 1.2rem;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.btn-zoom:hover:not(:disabled) {\r\n background: #555;\r\n}\r\n\r\n.btn-zoom:disabled {\r\n opacity: 0.3;\r\n cursor: not-allowed;\r\n}\r\n\r\n/* 移动端按钮优化 */\r\n@media (max-width: 768px) {\r\n .zoom-toolbar {\r\n display: none;\r\n }\r\n\r\n .btn:active {\r\n background: #555;\r\n transform: none;\r\n }\r\n\r\n .btn:hover:not(:disabled) {\r\n background: #555;\r\n transform: none;\r\n }\r\n\r\n .btn-catalogue {\r\n padding: 8px 12px;\r\n font-size: 0.8rem;\r\n border-radius: 16px;\r\n }\r\n\r\n .btn-catalogue .drag-handle {\r\n font-size: 16px;\r\n }\r\n\r\n .catalogue-icon {\r\n font-size: 0.9rem;\r\n }\r\n\r\n .catalogue-text {\r\n font-size: 0.8rem;\r\n }\r\n\r\n .zoom-hint {\r\n top: 10px;\r\n right: 10px;\r\n font-size: 0.75rem;\r\n }\r\n}\r\n\r\n/* 加载状态样式 */\r\n.loading-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(10px);\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 9999;\r\n animation: fadeIn 0.3s ease-out;\r\n}\r\n\r\n.loading-container {\r\n text-align: center;\r\n padding: 40px;\r\n background: white;\r\n border-radius: 20px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);\r\n max-width: 400px;\r\n width: 90%;\r\n}\r\n\r\n.loading-spinner {\r\n position: relative;\r\n width: 80px;\r\n height: 80px;\r\n margin: 0 auto 30px;\r\n}\r\n\r\n.spinner-ring {\r\n position: absolute;\r\n width: 100%;\r\n height: 100%;\r\n border: 3px solid transparent;\r\n border-top: 3px solid #007bff;\r\n border-radius: 50%;\r\n animation: spin 1.2s linear infinite;\r\n}\r\n\r\n.spinner-ring:nth-child(2) {\r\n width: 60px;\r\n height: 60px;\r\n top: 10px;\r\n left: 10px;\r\n border-top-color: #28a745;\r\n animation-delay: -0.4s;\r\n}\r\n\r\n.spinner-ring:nth-child(3) {\r\n width: 40px;\r\n height: 40px;\r\n top: 20px;\r\n left: 20px;\r\n border-top-color: #ffc107;\r\n animation-delay: -0.8s;\r\n}\r\n\r\n@keyframes spin {\r\n 0% { transform: rotate(0deg); }\r\n 100% { transform: rotate(360deg); }\r\n}\r\n\r\n.loading-text h3 {\r\n color: #333;\r\n font-size: 1.5rem;\r\n margin-bottom: 10px;\r\n font-weight: 600;\r\n}\r\n\r\n.loading-text p {\r\n color: #666;\r\n font-size: 1rem;\r\n margin-bottom: 20px;\r\n line-height: 1.5;\r\n}\r\n\r\n.loading-progress {\r\n width: 100%;\r\n height: 4px;\r\n background: #f0f0f0;\r\n border-radius: 2px;\r\n overflow: hidden;\r\n margin-top: 20px;\r\n}\r\n\r\n.progress-bar {\r\n height: 100%;\r\n background: linear-gradient(90deg, #007bff, #28a745, #ffc107);\r\n background-size: 200% 100%;\r\n animation: progressMove 2s ease-in-out infinite;\r\n border-radius: 2px;\r\n}\r\n\r\n@keyframes progressMove {\r\n 0% {\r\n transform: translateX(-100%);\r\n background-position: 200% 0;\r\n }\r\n 100% {\r\n transform: translateX(100%);\r\n background-position: -200% 0;\r\n }\r\n}\r\n\r\n/* 错误状态样式 */\r\n.error-container {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(10px);\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 9999;\r\n animation: fadeIn 0.3s ease-out;\r\n}\r\n\r\n.error-content {\r\n text-align: center;\r\n padding: 40px;\r\n background: white;\r\n border-radius: 20px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);\r\n max-width: 400px;\r\n width: 90%;\r\n}\r\n\r\n.error-icon {\r\n font-size: 4rem;\r\n margin-bottom: 20px;\r\n display: block;\r\n}\r\n\r\n.error-content h3 {\r\n color: #dc3545;\r\n font-size: 1.5rem;\r\n margin-bottom: 15px;\r\n font-weight: 600;\r\n}\r\n\r\n.error-content p {\r\n color: #666;\r\n font-size: 1rem;\r\n margin-bottom: 30px;\r\n line-height: 1.5;\r\n}\r\n\r\n.retry-btn {\r\n background: linear-gradient(45deg, #007bff, #0056b3);\r\n color: white;\r\n border: none;\r\n padding: 12px 30px;\r\n border-radius: 25px;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n cursor: pointer;\r\n transition: all 0.3s ease;\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 8px;\r\n box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);\r\n}\r\n\r\n.retry-btn:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);\r\n}\r\n\r\n.retry-btn:active {\r\n transform: translateY(0);\r\n}\r\n\r\n.retry-icon {\r\n display: inline-block;\r\n animation: rotateIcon 0.5s ease-in-out;\r\n}\r\n\r\n.retry-btn:hover .retry-icon {\r\n animation: rotateIcon 0.5s ease-in-out infinite;\r\n}\r\n\r\n@keyframes rotateIcon {\r\n from { transform: rotate(0deg); }\r\n to { transform: rotate(360deg); }\r\n}\r\n\r\n/* 成功提示样式 */\r\n.success-toast {\r\n position: fixed;\r\n top: 30px;\r\n right: 30px;\r\n background: linear-gradient(45deg, #28a745, #20c997);\r\n color: white;\r\n padding: 15px 25px;\r\n border-radius: 25px;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n box-shadow: 0 8px 25px rgba(40, 167, 69, 0.3);\r\n z-index: 10000;\r\n opacity: 0;\r\n transform: translateX(100px);\r\n transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n.success-toast.show {\r\n opacity: 1;\r\n transform: translateX(0);\r\n}\r\n\r\n.success-icon {\r\n font-size: 1.2rem;\r\n}\r\n\r\n@keyframes fadeIn {\r\n from {\r\n opacity: 0;\r\n transform: scale(0.9);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: scale(1);\r\n }\r\n}\r\n\r\n/* 移动端适配 */\r\n/* 翻页模式选择器样式 */\r\n.flip-mode-selector {\r\n position: fixed;\r\n top: 20px;\r\n left: 20px;\r\n z-index: 10000;\r\n}\r\n\r\n.mobile-flip-mode-selector {\r\n top: 15px;\r\n left: 15px;\r\n}\r\n\r\n.btn-flip-mode {\r\n background: linear-gradient(45deg, #6c5ce7, #a29bfe);\r\n color: white;\r\n border: 2px solid rgba(255, 255, 255, 0.3);\r\n padding: 10px 16px;\r\n border-radius: 20px;\r\n font-size: 0.9rem;\r\n font-weight: 600;\r\n box-shadow: 0 4px 12px rgba(108, 92, 231, 0.4);\r\n transition: all 0.2s ease;\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n cursor: pointer;\r\n}\r\n\r\n.btn-flip-mode:hover {\r\n background: linear-gradient(45deg, #5f4ed5, #8c7ae6);\r\n transform: translateY(-2px);\r\n box-shadow: 0 6px 16px rgba(108, 92, 231, 0.5);\r\n}\r\n\r\n.flip-mode-icon {\r\n font-size: 1rem;\r\n}\r\n\r\n.flip-mode-text {\r\n font-size: 0.85rem;\r\n}\r\n\r\n.flip-mode-menu {\r\n position: absolute;\r\n top: 100%;\r\n left: 0;\r\n margin-top: 8px;\r\n background: white;\r\n border-radius: 12px;\r\n box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);\r\n overflow: hidden;\r\n min-width: 150px;\r\n animation: slideDown 0.2s ease-out;\r\n}\r\n\r\n@keyframes slideDown {\r\n from {\r\n opacity: 0;\r\n transform: translateY(-10px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n.flip-mode-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 12px 16px;\r\n cursor: pointer;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.flip-mode-item:hover {\r\n background: #f5f5f5;\r\n}\r\n\r\n.flip-mode-item-active {\r\n background: linear-gradient(45deg, #6c5ce7, #a29bfe);\r\n color: white;\r\n}\r\n\r\n.flip-mode-item-active:hover {\r\n background: linear-gradient(45deg, #5f4ed5, #8c7ae6);\r\n}\r\n\r\n.flip-mode-item-icon {\r\n font-size: 1.1rem;\r\n}\r\n\r\n.flip-mode-item-label {\r\n font-size: 0.9rem;\r\n font-weight: 500;\r\n}\r\n\r\n/* 滑动模式样式 */\r\n.slide-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: hidden;\r\n position: relative;\r\n}\r\n\r\n.slide-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.slide-container {\r\n display: flex;\r\n height: 100%;\r\n will-change: transform;\r\n}\r\n\r\n.slide-page {\r\n flex: 0 0 100%;\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n}\r\n\r\n.slide-page-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n}\r\n\r\n.slide-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n/* 淡入淡出模式样式 */\r\n.fade-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: hidden;\r\n position: relative;\r\n}\r\n\r\n.fade-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.fade-page-container {\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n}\r\n\r\n.fade-page-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n}\r\n\r\n.fade-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n.fade-page-enter-active,\r\n.fade-page-leave-active {\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.fade-page-enter,\r\n.fade-page-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n/* 垂直滚动模式样式 */\r\n.scroll-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow-y: auto;\r\n overflow-x: hidden;\r\n position: relative;\r\n scroll-behavior: smooth;\r\n}\r\n\r\n.scroll-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.scroll-container {\r\n width: 100%;\r\n}\r\n\r\n.scroll-page {\r\n width: 100%;\r\n min-height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n padding: 20px 0;\r\n box-sizing: border-box;\r\n}\r\n\r\n.scroll-page-image {\r\n max-width: 100%;\r\n max-height: none;\r\n width: auto;\r\n}\r\n\r\n.scroll-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n min-height: 300px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n/* 滚动条样式 */\r\n.scroll-viewer::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n.scroll-viewer::-webkit-scrollbar-track {\r\n background: #f1f1f1;\r\n border-radius: 4px;\r\n}\r\n\r\n.scroll-viewer::-webkit-scrollbar-thumb {\r\n background: #c1c1c1;\r\n border-radius: 4px;\r\n}\r\n\r\n.scroll-viewer::-webkit-scrollbar-thumb:hover {\r\n background: #a1a1a1;\r\n}\r\n\r\n/* 截断特效模式样式 */\r\n.clip-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: hidden;\r\n position: relative;\r\n perspective: 1200px;\r\n}\r\n\r\n.clip-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.clip-pages-wrapper {\r\n width: 100%;\r\n height: 100%;\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.clip-page {\r\n position: absolute;\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #f8f9fa;\r\n}\r\n\r\n.clip-page-current {\r\n z-index: 2;\r\n clip-path: inset(0 0 0 0);\r\n}\r\n\r\n.clip-page-next {\r\n z-index: 1;\r\n opacity: 0.5;\r\n transform: scale(0.95);\r\n filter: blur(2px);\r\n}\r\n\r\n.clip-page-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n border-radius: 8px;\r\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);\r\n}\r\n\r\n.clip-page-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n/* 截断动画 */\r\n.clip-current-enter-active {\r\n animation: clipIn 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.clip-current-leave-active {\r\n animation: clipOut 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n@keyframes clipIn {\r\n 0% {\r\n clip-path: inset(0 100% 0 0);\r\n transform: translateX(50px);\r\n opacity: 0;\r\n }\r\n 100% {\r\n clip-path: inset(0 0 0 0);\r\n transform: translateX(0);\r\n opacity: 1;\r\n }\r\n}\r\n\r\n@keyframes clipOut {\r\n 0% {\r\n clip-path: inset(0 0 0 0);\r\n transform: translateX(0);\r\n opacity: 1;\r\n }\r\n 100% {\r\n clip-path: inset(0 0 0 100%);\r\n transform: translateX(-50px);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n/* 卡片风格模式样式 */\r\n.card-viewer {\r\n width: 1600px;\r\n height: 1000px;\r\n overflow: visible;\r\n position: relative;\r\n perspective: 1500px;\r\n}\r\n\r\n.card-viewer.mobile-mode {\r\n width: 100%;\r\n max-width: none;\r\n height: 85vh;\r\n max-height: 85vh;\r\n}\r\n\r\n.card-stack {\r\n width: 100%;\r\n height: 100%;\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n transform-style: preserve-3d;\r\n}\r\n\r\n.card-transition-group {\r\n width: 100%;\r\n height: 100%;\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.card-item {\r\n position: absolute;\r\n width: 70%;\r\n height: 90%;\r\n transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n transform-style: preserve-3d;\r\n cursor: pointer;\r\n}\r\n\r\n.card-content {\r\n width: 100%;\r\n height: 100%;\r\n background: white;\r\n border-radius: 16px;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 8px 25px rgba(0, 0, 0, 0.2);\r\n overflow: hidden;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n position: relative;\r\n}\r\n\r\n.card-item-current .card-content {\r\n box-shadow: 0 30px 80px rgba(0, 0, 0, 0.35), 0 15px 40px rgba(0, 0, 0, 0.25);\r\n}\r\n\r\n.card-image {\r\n max-width: 100%;\r\n max-height: 100%;\r\n object-fit: contain;\r\n}\r\n\r\n.card-placeholder {\r\n font-size: 2rem;\r\n color: #666;\r\n text-align: center;\r\n}\r\n\r\n.card-page-number {\r\n position: absolute;\r\n bottom: 15px;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n background: rgba(0, 0, 0, 0.7);\r\n color: white;\r\n padding: 6px 16px;\r\n border-radius: 20px;\r\n font-size: 0.85rem;\r\n font-weight: 500;\r\n}\r\n\r\n.card-item-prev .card-page-number,\r\n.card-item-next .card-page-number {\r\n opacity: 0;\r\n}\r\n\r\n/* 卡片切换动画 */\r\n.card-flip-enter-active,\r\n.card-flip-leave-active {\r\n transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.card-flip-enter {\r\n opacity: 0;\r\n transform: translateX(100%) scale(0.8) rotateY(-30deg);\r\n}\r\n\r\n.card-flip-leave-to {\r\n opacity: 0;\r\n transform: translateX(-100%) scale(0.8) rotateY(30deg);\r\n}\r\n\r\n/* 卡片悬停效果 */\r\n.card-item-current:hover .card-content {\r\n box-shadow: 0 35px 90px rgba(0, 0, 0, 0.4), 0 18px 50px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n/* 移动端卡片样式 */\r\n@media (max-width: 768px) {\r\n .card-item {\r\n width: 85%;\r\n height: 85%;\r\n }\r\n \r\n .card-item-prev,\r\n .card-item-next {\r\n display: none;\r\n }\r\n \r\n .card-content {\r\n border-radius: 12px;\r\n }\r\n \r\n .card-page-number {\r\n font-size: 0.75rem;\r\n padding: 4px 12px;\r\n }\r\n \r\n .clip-page-next {\r\n display: none;\r\n }\r\n}\r\n\r\n/* 移动端翻页模式选择器样式 */\r\n@media (max-width: 768px) {\r\n .btn-flip-mode {\r\n padding: 8px 12px;\r\n font-size: 0.8rem;\r\n border-radius: 16px;\r\n }\r\n \r\n .flip-mode-icon {\r\n font-size: 0.9rem;\r\n }\r\n \r\n .flip-mode-text {\r\n font-size: 0.75rem;\r\n }\r\n \r\n .flip-mode-menu {\r\n min-width: 130px;\r\n }\r\n \r\n .flip-mode-item {\r\n padding: 10px 14px;\r\n }\r\n \r\n .flip-mode-item-icon {\r\n font-size: 1rem;\r\n }\r\n \r\n .flip-mode-item-label {\r\n font-size: 0.8rem;\r\n }\r\n}\r\n\r\n@media (max-width: 768px) {\r\n .loading-container,\r\n .error-content {\r\n padding: 30px 20px;\r\n margin: 20px;\r\n }\r\n \r\n .loading-spinner {\r\n width: 60px;\r\n height: 60px;\r\n }\r\n \r\n .spinner-ring:nth-child(2) {\r\n width: 45px;\r\n height: 45px;\r\n top: 7.5px;\r\n left: 7.5px;\r\n }\r\n \r\n .spinner-ring:nth-child(3) {\r\n width: 30px;\r\n height: 30px;\r\n top: 15px;\r\n left: 15px;\r\n }\r\n \r\n .loading-text h3,\r\n .error-content h3 {\r\n font-size: 1.3rem;\r\n }\r\n \r\n .loading-text p,\r\n .error-content p {\r\n font-size: 0.9rem;\r\n }\r\n \r\n .success-toast {\r\n top: 20px;\r\n right: 20px;\r\n left: 20px;\r\n right: 20px;\r\n font-size: 0.9rem;\r\n padding: 12px 20px;\r\n }\r\n \r\n .retry-btn {\r\n padding: 10px 25px;\r\n font-size: 0.9rem;\r\n }\r\n}\r\n\r\n</style>\r\n","import mod from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=script&lang=js\"","// extracted by mini-css-extract-plugin\nexport {};","export * from \"-!../../node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../node_modules/@vue/vue-loader-v15/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./BookReader.vue?vue&type=style&index=0&id=4702ca70&prod&scoped=true&lang=css\"","import { render, staticRenderFns } from \"./BookReader.vue?vue&type=template&id=4702ca70&scoped=true\"\nimport script from \"./BookReader.vue?vue&type=script&lang=js\"\nexport * from \"./BookReader.vue?vue&type=script&lang=js\"\nimport style0 from \"./BookReader.vue?vue&type=style&index=0&id=4702ca70&prod&scoped=true&lang=css\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"4702ca70\",\n null\n \n)\n\nexport default component.exports","import BookReader from './components/BookReader.vue'\r\nimport BookCatalogueDrawer from './components/BookCatalogueDrawer.vue'\r\n\r\nconst components = {\r\n BookReader,\r\n BookCatalogueDrawer\r\n}\r\n\r\nconst install = function(Vue) {\r\n if (install.installed) return\r\n install.installed = true\r\n Object.keys(components).forEach(key => {\r\n Vue.component(key, components[key])\r\n })\r\n}\r\n\r\nif (typeof window !== 'undefined' && window.Vue) {\r\n install(window.Vue)\r\n}\r\n\r\nexport default {\r\n install,\r\n BookReader,\r\n BookCatalogueDrawer\r\n}\r\n\r\nexport {\r\n BookReader,\r\n BookCatalogueDrawer\r\n}\r\n","import './setPublicPath'\nimport mod from '~entry'\nexport default mod\nexport * from '~entry'\n"],"names":[],"ignoreList":[],"sourceRoot":""}