mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
Merge 1ec1dd3b39 into 5d4d01a797
This commit is contained in:
commit
9455b3ce5a
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cloudreve-frontend",
|
||||
"version": "3.8.3",
|
||||
"name": "cloudreve-frontend-plus",
|
||||
"version": "3.8.3+1.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.6.0",
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
"react-async-script": "^1.1.1",
|
||||
"react-color": "^2.18.0",
|
||||
"react-content-loader": "^5.0.2",
|
||||
"react-dev-utils": "9",
|
||||
"react-dev-utils": "^9.1.0",
|
||||
"react-dnd": "^9.5.1",
|
||||
"react-dnd-html5-backend": "^9.5.1",
|
||||
"react-dom": "^16.12.0",
|
||||
|
|
@ -113,7 +113,8 @@
|
|||
"webpack": "4.41.0",
|
||||
"webpack-dev-server": "3.11.3",
|
||||
"webpack-manifest-plugin": "2.1.1",
|
||||
"workbox-webpack-plugin": "4.3.1"
|
||||
"workbox-webpack-plugin": "4.3.1",
|
||||
"yarn": "^1.22.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
|
|
@ -189,7 +190,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"eslint-plugin-react-hooks": "^4.0.0"
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"prettier": "2.7.1"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<meta name="theme-color" content="" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="description" content="{siteDes}">
|
||||
<meta name="keywords" content="{siteKeywords}">
|
||||
<title>{siteName}</title>
|
||||
<script>
|
||||
window.subTitle = "{siteName}";
|
||||
|
|
@ -18,6 +19,6 @@
|
|||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
{siteScript}
|
||||
</body>
|
||||
{siteScript}
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"myFiles": "My Files",
|
||||
"myShare": "Shared",
|
||||
"remoteDownload": "Remote Download",
|
||||
"connect": "Connect",
|
||||
"connect": "Connect & Mount",
|
||||
"taskQueue": "Task Queue",
|
||||
"setting": "Settings",
|
||||
"videos": "Videos",
|
||||
|
|
@ -200,6 +200,8 @@
|
|||
"remoteDownloadURL": "Download target URL",
|
||||
"remoteDownloadURLDescription": "Paste the download URL, one URL per line, support HTTP(s) / FTP / Magnet link",
|
||||
"remoteDownloadDst": "Download to",
|
||||
"remoteDownloadNode": "Download node",
|
||||
"remoteDownloadNodeAuto": "Auto dispatch",
|
||||
"createTask": "Creat task",
|
||||
"downloadTo": "Download to <0>{{name}}</0>",
|
||||
"decompressTo": "Decompress to",
|
||||
|
|
@ -498,5 +500,92 @@
|
|||
"aboutCloudreve": "About Cloudreve",
|
||||
"githubRepo": "GitHub Repository",
|
||||
"homepage": "Homepage"
|
||||
},
|
||||
"vas": {
|
||||
"loginWithQQ": "Sign in with QQ",
|
||||
"quota": "Quota",
|
||||
"exceedQuota": "Your used capacity has exceeded the quota, please delete the extra files or buy more storage as soon as possible.",
|
||||
"extendStorage": "Buy storage",
|
||||
"folderPolicySwitched": "Folder storage policy is switched.",
|
||||
"switchFolderPolicy": "Switching folder storage policies",
|
||||
"setPolicyForFolder": "Set the storage policy for the current folder: ",
|
||||
"manageMount": "Manage mounts",
|
||||
"saveToMyFiles": "Save to my files",
|
||||
"report": "Report abuse",
|
||||
"migrateStoragePolicy": "Migrate storage policy",
|
||||
"fileSaved": "File(s) saved.",
|
||||
"sharePurchaseTitle": "Sure you want to pay {{score}} credits for this share?",
|
||||
"sharePurchaseDescription": "After purchase, you are free to preview and download all the contents of this share without repeated deduction for a certain period of time. If you have already purchased, please ignore this message.",
|
||||
"payToDownload": "Paid credits to download",
|
||||
"creditToBePaid": "Credits to be paid per person per download",
|
||||
"creditGainPredict": "Expected {{num}} points to you per download",
|
||||
"creditPrice": " (Cost {{num}} credits)",
|
||||
"creditFree": " (Credits free)",
|
||||
"cancelSubscription": "The cancellation is successful and the change will take effect in a few minutes.",
|
||||
"qqUnlinked": "Unlinked from QQ account.",
|
||||
"groupExpire": " Expires <0></0>",
|
||||
"manuallyCancelSubscription": "Unsubscribe current user group",
|
||||
"qqAccount": "QQ account",
|
||||
"connect": "Connect",
|
||||
"unlink": "Unlink",
|
||||
"credits": "Credits",
|
||||
"cancelSubscriptionTitle": "Unsubscribe",
|
||||
"cancelSubscriptionWarning": "You will return to the initial user group and the credits paid is not refundable, are you sure you want to continue?",
|
||||
"mountPolicy": "Mount storage policy",
|
||||
"mountDescription": "After mounting a storage policy to a folder, new files uploaded to this folder or sub-folders will be stored using the mounted storage policy. Copying and moving to this folder will not apply the mounted storage policy; when multiple parent folders are specified, the storage policy of the closest parent folder will be selected.",
|
||||
"mountNewFolder": "Mount new folder",
|
||||
"nsfw": "NSFW",
|
||||
"malware": "Malware",
|
||||
"copyright": "Copyright",
|
||||
"inappropriateStatements": "Inappropriate statements",
|
||||
"other": "Other",
|
||||
"groupBaseQuota": "Group base quota",
|
||||
"validPackQuota": "Storage packs",
|
||||
"used": "Used",
|
||||
"total": "Total",
|
||||
"validStoragePack": "Valid storage packs",
|
||||
"buyStoragePack": "Buy storage packs",
|
||||
"useGiftCode": "Redeem with gift code",
|
||||
"packName": "Pack name",
|
||||
"activationDate": "Activation date",
|
||||
"validDuration": "Duration",
|
||||
"expiredAt": "Expire at",
|
||||
"days": "$t(share.days, {\"count\": {{num}} })",
|
||||
"pleaseInputGiftCode": "Please enter gift code.",
|
||||
"pleaseSelectAStoragePack": "Please select a storage pack.",
|
||||
"selectPaymentMethod": "Payment: ",
|
||||
"noAvailableMethod": "No available payment method",
|
||||
"alipay": "Alipay",
|
||||
"wechatPay": "Wechat Pay",
|
||||
"payByCredits": "Credits",
|
||||
"purchaseDuration": "Duration unit: ",
|
||||
"creditsNum": "Credits qyt:",
|
||||
"store": "Store",
|
||||
"storagePacks": "Storage packs",
|
||||
"membership": "Memberships",
|
||||
"buyCredits": "Credits",
|
||||
"subtotal": "Subtotal: ",
|
||||
"creditsTotalNum": "{{num}} credits",
|
||||
"checkoutNow": "Buy now",
|
||||
"recommended": "Recommended",
|
||||
"enterGiftCode": "Enter gift code",
|
||||
"qrcodeAlipay": "Please use Alipay to scan the QR code below to complete the payment, this page will be automatically refreshed after the payment is completed.",
|
||||
"qrcodeWechat": "Please use Wechat to scan the QR code below to complete the payment, this page will be automatically refreshed after the payment is completed.",
|
||||
"qrcodeCustom": "Please scan the QR code below to complete the payment, this page will be automatically refreshed after the payment is completed.",
|
||||
"paymentCompleted": "Payment completed",
|
||||
"productDelivered": "Your purchase are processed.",
|
||||
"confirmRedeem": "Redeem",
|
||||
"productName": "Product: ",
|
||||
"qyt": "Qyt: ",
|
||||
"duration": "Duration: ",
|
||||
"subscribe": "Subscribe",
|
||||
"selected": "Selected: ",
|
||||
"paymentQrcode": "Payment QRCode",
|
||||
"validDurationDays": "Duration: $t(share.days, {\"count\": {{num}} })",
|
||||
"reportSuccessful": "Report submitted.",
|
||||
"additionalDescription": "Additional description",
|
||||
"announcement": "Announcement",
|
||||
"dontShowAgain": "Don't show again",
|
||||
"openPaymentLink": "Open payment link"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,5 +74,17 @@
|
|||
"50005": "Internal error.",
|
||||
"50010": "Desired node is unavailable.",
|
||||
"50011": "Failed to query file metadata."
|
||||
},
|
||||
"vasErrors": {
|
||||
"40031": "This email provider is forbidden, please change to another one.",
|
||||
"40059": "You cannot save your own share.",
|
||||
"40062": "Insufficient credits.",
|
||||
"40063": "Your current membership has not yet expired, please go to settings page to manually unsubscribe the membership. first.",
|
||||
"40064": "You are already in this membership.",
|
||||
"40065": "Invalid gift code.",
|
||||
"40066": "You already have a QQ account linked, please unlink it first.",
|
||||
"40067": "This QQ account is already linked to other account.",
|
||||
"40068": "This QQ account is not linked to any account.",
|
||||
"40072": "You are administrator, you cannot purchase other group."
|
||||
}
|
||||
}
|
||||
|
|
@ -51,11 +51,12 @@
|
|||
"privateShares": "Private shares",
|
||||
"homepage": "Homepage",
|
||||
"documents": "Documents",
|
||||
"github": "Github repository",
|
||||
"forum": "Forum",
|
||||
"forumLink": "https://github.com/cloudreve/Cloudreve/discussions",
|
||||
"telegramGroup": "Telegram group",
|
||||
"telegramGroupLink": "https://t.me/cloudreve_global",
|
||||
"buyPro": "Upgrade to Pro",
|
||||
"buyPro": "Donate to developers",
|
||||
"publishedAt": "published at <0></0>",
|
||||
"newsTag": "announcements"
|
||||
},
|
||||
|
|
@ -67,12 +68,17 @@
|
|||
"mainTitleDes": "Main title of the website.",
|
||||
"subTitle": "Subtitle",
|
||||
"subTitleDes": "Subtitle of the website.",
|
||||
"siteKeywords": "Site keywords",
|
||||
"siteKeywordsDes": "Keywords of the website, separated by commas.",
|
||||
"siteDescription": "Site description",
|
||||
"siteDescriptionDes": "Description of the website, which may be displayed in the shared page summary.",
|
||||
"siteURL": "Site URL",
|
||||
"siteURLDes": "Very important, please make sure it is consistent with the actual situation. When using cloud storage policy and payment platform, please fill in the address that can be accessed by WAN.",
|
||||
"customFooterHTML": "Custom footer HTML",
|
||||
"customFooterHTMLDes": "Custom HTML code inserted at the bottom of the page.",
|
||||
"announcement": "Announcement",
|
||||
"announcementDes": "Announcements displayed to logged-in users. Blank value will not be displayed. After this content is changed, all users will see the announcement again.",
|
||||
"supportHTML": "Enter HTML or plain text.",
|
||||
"pwa": "Progressive Web Application (PWA)",
|
||||
"smallIcon": "Small icon",
|
||||
"smallIconDes": "URL of the small icon with the ico as extension",
|
||||
|
|
@ -213,6 +219,7 @@
|
|||
"officePreviewServiceSrcB64Des": " Base64 encoded file URL",
|
||||
"officePreviewServiceName": "File name",
|
||||
"thumbnails": "Thumbnails",
|
||||
"localOnlyInfo": "The following settings only apply to local storage policies.",
|
||||
"thumbnailDoc": "For more information about thumbnail, see the <0>document</0>.",
|
||||
"thumbnailDocLink":"https://docs.cloudreve.org/v/en/use/thumbnails",
|
||||
"thumbnailBasic": "Basic",
|
||||
|
|
@ -688,6 +695,8 @@
|
|||
"nameOfGroupDes": "Name of the group.",
|
||||
"storagePolicy": "Storage policy",
|
||||
"storageDes": "Select the storage policy that this group use.",
|
||||
"availablePolicies": "Available storage policies",
|
||||
"availablePoliciesDes": "Select the storage policies that this group can use.",
|
||||
"initialStorageQuota": "Initial storage quota",
|
||||
"initialStorageQuotaDes": "Max storage can used by single user under this group.",
|
||||
"downloadSpeedLimit": "Max download speed",
|
||||
|
|
@ -718,8 +727,14 @@
|
|||
"compressSizeDes": "The maximum total file size of compression jobs that can be created by the user, fill in 0 to indicate no limit.",
|
||||
"decompressSize": "Maximum file size to be decompressed",
|
||||
"decompressSizeDes": "The maximum total file size of decompression jobs that can be created by the user, fill in 0 to indicate no limit.",
|
||||
"migratePolicy": "Migrate storage policy",
|
||||
"migratePolicyDes": "Whether the user creates a storage policy migration task.",
|
||||
"allowSelectNode": "Allow select node",
|
||||
"allowSelectNodeDes": "When enabled, user can select preferred slave node before creating remote download task. When disabled, the node will be load-balanced by system.",
|
||||
"redirectedSource": "Use redirected source link",
|
||||
"redirectedSourceDes": "When enabled, the source link to the file obtained by the user will be redirected by Cloudreve with a shorter link. When disabled, the source link to the file obtained by the user becomes the original URL to the file. Some policies produce non-redirected source links that do not remain persistent, see <0>Comparing Storage Policies</0>.",
|
||||
"availableNodes": "Available nodes",
|
||||
"availableNodesDes": "Select the slave nodes that this group can use to create remote download tasks. Empty list means all nodes are available. Users can only select or be assigned nodes within this list by load balancer.",
|
||||
"advanceDelete": "Allow advanced file deletion options",
|
||||
"advanceDeleteDes": "Once enabled, users can choose whether to force deletion and whether to unlink only physical links when deleting files. These options are similar to the administration dashboard when deleting files."
|
||||
},
|
||||
|
|
@ -821,5 +836,151 @@
|
|||
"editGroupDes": "When you add multiple nodes that can be used for remote downloads, the master node will send remote download requests to these nodes in turn for processing. You may also need to <0>go here</0> to enable remote download permissions for the corresponding groups.",
|
||||
"lastProgress": "Last progress",
|
||||
"errorMsg": "Error message"
|
||||
},
|
||||
"vas": {
|
||||
"vas": "VAS",
|
||||
"reports": "Reports",
|
||||
"orders": "Orders",
|
||||
"initialFiles": "Initial files",
|
||||
"filterEmailProvider": "Filter email provider",
|
||||
"filterEmailProviderWhitelist": "Whitelist",
|
||||
"initialFilesDes": "Specify the files that the user initially owns after signups. Enter a file ID to search existing files.",
|
||||
"filterEmailProviderDisabled": "Disabled",
|
||||
"filterEmailProviderDes": "Filter signup email domains.",
|
||||
"appLinkDes": "Will be displayed in mobile client, leave empty to hide menu item, This setting will take effect only if VOL license is valid.",
|
||||
"filterEmailProviderBlacklist": "Blacklist",
|
||||
"filterEmailProviderRuleDes": "Separate multiple fields with a semi-colon comma.",
|
||||
"filterEmailProviderRule": "Email domain filter rules",
|
||||
"qqConnectHint": "When creating the application, please fill in the callback URL: {{url}}",
|
||||
"enableQQConnectDes": "Whether to allow binding QQ, use QQ to login website.",
|
||||
"loginWithoutBindingDes": "After enabled, if a user logs in using QQ but does not have a registered user that is bound to it, the system will create a user for them and log them in. Users created in this way will only be able to log in using QQ in the future.",
|
||||
"qqConnect": "QQ Connect",
|
||||
"enableQQConnect": "Enable QQ Connect",
|
||||
"appidDes": "The APP ID obtained from the application management page.",
|
||||
"appForum": "User forum URL",
|
||||
"loginWithoutBinding": "Login without registration",
|
||||
"appKeyDes": "The APP KEY obtained from the application management page.",
|
||||
"appid": "APP ID",
|
||||
"overuseReminderDes": "Reminder email template sent to users after their capacity exceeds the limit due to expired VAS.",
|
||||
"storagePack": "Storage packs",
|
||||
"giftCodes": "Gift codes",
|
||||
"appKey": "APP KEY",
|
||||
"overuseReminder": "Overuse reminder",
|
||||
"enable": "Enable",
|
||||
"appFeedback": "Feedback URL",
|
||||
"vasSetting": "VAS settings",
|
||||
"appIDDes": "APPID of payment application.",
|
||||
"purchasableGroups": "Memberships",
|
||||
"rsaPrivateDes": "The RSA2 (SHA256) private key for the payment application, typically generated by you. For details, refer to <0>Generating RSA Keys</0>.",
|
||||
"alipayPublicKeyDes": "Provided by Alipay, available in Application Management - Application Information - API Signing Method.",
|
||||
"applicationID": "Application ID",
|
||||
"alipay": "Alipay",
|
||||
"appID": "App- ID",
|
||||
"merchantID": "Merchant number",
|
||||
"customPaymentEndpointDes":"URL to be requested when creating a payment order.",
|
||||
"rsaPrivate": "RSA application private key",
|
||||
"apiV3Secret": "API v3 secret",
|
||||
"alipayPublicKey": "Alipay public key",
|
||||
"mcCertificateSerial": "Merchant certificate serial number",
|
||||
"mcAPISecret": "Merchant API Secrey",
|
||||
"payjs": "PAYJS",
|
||||
"wechatPay": "WeChat Pay",
|
||||
"applicationIDDes": "Public number or mobile application appid applied by merchants.",
|
||||
"mcNumber": "Merchant number",
|
||||
"customPaymentEndpoint":"Payment API URL",
|
||||
"merchantIDDes": "The merchant number generated and issued by WeChat Pay.",
|
||||
"communicationSecret": "Communication key",
|
||||
"apiV3SecretDes": "The merchant needs to set the secret in [Merchant Platform] - [API Security] before the request WeChat Pay. The length of the key is 32 bytes.",
|
||||
"banBufferPeriod": "Suspend buffer period (seconds)",
|
||||
"allowSellShares": "Allow pricing for shares",
|
||||
"creditPriceRatio": "Credit arrival rate (%)",
|
||||
"mcCertificateSerialDes": "Navigate to [API Security] - [API Certificate] - [View Certificate] to view the merchant API certificate serial number.",
|
||||
"mcAPISecretDes": "Content of the secret file apiclient_key.pem.",
|
||||
"creditPrice": "Credit price (penny)",
|
||||
"customPaymentSecretDes": "Secret key for signing payment requests.",
|
||||
"payjsWarning": "This service is provided by <0>PAYJS</0>, a third-party platform, and any disputes arising from it are not the responsibility of Cloudreve developers.",
|
||||
"add": "Add",
|
||||
"mcNumberDes": "Available in the PAYJS admin panel home page.",
|
||||
"price": "Price",
|
||||
"size": "Size",
|
||||
"orCredits": " Or {{num}} credits",
|
||||
"otherSettings": "Other Settings",
|
||||
"banBufferPeriodDes": "The maximum length of time that a user can maintain the capacity overage status, beyond which the user will be suspend by the system.",
|
||||
"yes": "Yes",
|
||||
"customPaymentNameDes": "Name of the payment method used to display to the user.",
|
||||
"allowSellSharesDes": "Once enabled users can set a credit price for sharing and credit will be deducted for downloading.",
|
||||
"productName": "Product name",
|
||||
"creditPriceRatioDes": "The rate of credits actually arriving to the sharer for the purchase of a share with a set price for download.",
|
||||
"code": "Code",
|
||||
"invalidProduct": "Invalid product",
|
||||
"notUsed": "Not used",
|
||||
"creditPriceDes": "Price when recharging credits",
|
||||
"allowReportShare": "Allow report sharing",
|
||||
"allowReportShareDes": "When enabled, any user can report sharing and there is a risk of the database filling up.",
|
||||
"name": "Name",
|
||||
"addStoragePack": "Add storage pack",
|
||||
"customPaymentName": "Payment method name",
|
||||
"duration": "Duration",
|
||||
"productNameDes": "Product display name",
|
||||
"actions": "Actions",
|
||||
"durationDay": "Duration (day)",
|
||||
"priceYuan": "Price (Yuan)",
|
||||
"priceCredits": "Price (Credits)",
|
||||
"highlight": "Highlight",
|
||||
"no": "No",
|
||||
"editMembership": "Edit membership",
|
||||
"customPaymentDocumentLink":"https://docs.cloudreve.org/v/en/use/pro/pay",
|
||||
"qyt": "Qyt.",
|
||||
"group": "Group",
|
||||
"status": "Status",
|
||||
"durationGroupDes": "The validity of the purchase time of the user group unit upgraded after the purchase.",
|
||||
"productDescription": "Product description (Once per line)",
|
||||
"highlightDes": "After enabled, it will be highlighted on the product selection page.",
|
||||
"used": "Used",
|
||||
"generatingResult": "Result",
|
||||
"numberOfCodes": "Number of codes",
|
||||
"customPaymentDes":"Bridge to other third party payment platforms by implementing Cloudreve compatible payment interfaces, please refer to <0>Official Documentation</0> for details.",
|
||||
"editStoragePack": "Edit storage pack",
|
||||
"linkedProduct": "Linked product",
|
||||
"packSizeDes": "Size of storage pack",
|
||||
"productQytDes": "For credit products, this is the number of points and other products are multiples of durations.",
|
||||
"freeDownloadDes": "After enabled, user can download paid shares for free.",
|
||||
"markSuccessful": "Marked successfully.",
|
||||
"durationDayDes": "Valid duration of each storage pack.",
|
||||
"packPriceDes": "Price of storage pack.",
|
||||
"reportedContent": "Reported content",
|
||||
"customPayment": "Custom payment provider",
|
||||
"priceCreditsDes": "The price when using credits to buy, fill in 0 means you can't use credits to buy.",
|
||||
"description": "Description",
|
||||
"addMembership": "Add membership",
|
||||
"invalid": "[Invalid]",
|
||||
"orderDeleted": "Order deleted.",
|
||||
"product": "Product",
|
||||
"groupDes": "User groups upgraded after purchase.",
|
||||
"groupPriceDes": "Membership price",
|
||||
"paidBy": "Paid with",
|
||||
"showAppPromotion": "Show app promotion page",
|
||||
"showAppPromotionDes": "After enabled, user can see the guidance page for mobile application in \"Connect & Mount\" page.",
|
||||
"productDescriptionDes": "Description of the product displayed on the purchase page.",
|
||||
"unpaid": "Unpaid",
|
||||
"generateGiftCode": "Generate gift codes",
|
||||
"shareLink": "Shared link",
|
||||
"iosVol": "iOS client volume license (VOL)",
|
||||
"syncLicense": "Sync License",
|
||||
"numberOfCodesDes": "Number of gift codes to generate.",
|
||||
"productQyt": "Product qyt.",
|
||||
"freeDownload": "Download shared files for free",
|
||||
"credits": "Credits",
|
||||
"markAsResolved": "Mark as resolved",
|
||||
"reason": "Reason",
|
||||
"reportTime": "Reported at",
|
||||
"deleteShare": "Delete share link",
|
||||
"orderName": "Name",
|
||||
"orderNumber": "Order No.",
|
||||
"orderOwner": "Created by",
|
||||
"paid": "Paid",
|
||||
"volPurchase": "The client VOL license needs to be purchased separately from the <0>License Management Dashboard</0>. The VOL license allows your users to connect to your site using the <1>Cloudreve iOS</1> for free, without the need for users to pay for a subscription for the iOS app itself. After purchasing a license, please click \"Sync License\" below.",
|
||||
"mobileApp": "Mobile application",
|
||||
"volSynced": "VOL synced."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"myFiles": "我的文件",
|
||||
"myShare": "我的分享",
|
||||
"remoteDownload": "离线下载",
|
||||
"connect": "连接",
|
||||
"connect": "连接与挂载",
|
||||
"taskQueue": "任务队列",
|
||||
"setting": "个人设置",
|
||||
"videos": "视频",
|
||||
|
|
@ -200,6 +200,8 @@
|
|||
"remoteDownloadURL": "下载链接",
|
||||
"remoteDownloadURLDescription": "输入文件下载地址,一行一个,支持 HTTP(s) / FTP / 磁力链",
|
||||
"remoteDownloadDst": "下载至",
|
||||
"remoteDownloadNode": "下载节点",
|
||||
"remoteDownloadNodeAuto": "自动分配",
|
||||
"createTask": "创建任务",
|
||||
"downloadTo": "下载至 <0>{{name}}</0>",
|
||||
"decompressTo": "解压缩至",
|
||||
|
|
@ -498,5 +500,92 @@
|
|||
"aboutCloudreve": "关于 Cloudreve",
|
||||
"githubRepo": "GitHub 仓库",
|
||||
"homepage": "主页"
|
||||
},
|
||||
"vas": {
|
||||
"loginWithQQ": "使用 QQ 登录",
|
||||
"quota": "容量配额",
|
||||
"exceedQuota": "您的已用容量已超过容量配额,请尽快删除多余文件或购买容量",
|
||||
"extendStorage": "扩容",
|
||||
"folderPolicySwitched": "目录存储策略已切换",
|
||||
"switchFolderPolicy": "切换目录存储策略",
|
||||
"setPolicyForFolder": "为当前目录设置存储策略: ",
|
||||
"manageMount": "管理绑定",
|
||||
"saveToMyFiles": "保存到我的文件",
|
||||
"report": "举报",
|
||||
"migrateStoragePolicy": "转移存储策略",
|
||||
"fileSaved": "文件已保存",
|
||||
"sharePurchaseTitle": "确定要支付 {{score}} 积分 购买此分享?",
|
||||
"sharePurchaseDescription": "购买后,您可以自由预览、下载此分享的所有内容,一定期限内不会重复扣费。如果您已购买,请忽略此提示。",
|
||||
"payToDownload": "付积分下载",
|
||||
"creditToBePaid": "每人次下载需支付的积分",
|
||||
"creditGainPredict": "预计每人次下载可到账 {{num}} 积分",
|
||||
"creditPrice": " ({{num}} 积分)",
|
||||
"creditFree": " (免积分)",
|
||||
"cancelSubscription": "解约成功,更改会在数分钟后生效",
|
||||
"qqUnlinked": "已解除与QQ账户的关联",
|
||||
"groupExpire": " <0></0> 过期",
|
||||
"manuallyCancelSubscription": "手动解约当前用户组",
|
||||
"qqAccount": "QQ账号",
|
||||
"connect": "绑定",
|
||||
"unlink": "解除绑定",
|
||||
"credits": "积分",
|
||||
"cancelSubscriptionTitle": "解约用户组",
|
||||
"cancelSubscriptionWarning": "将要退回到初始用户组,且所支付金额无法退还,确定要继续吗?",
|
||||
"mountPolicy": "存储策略绑定",
|
||||
"mountDescription": "为目录绑定存储策略后,上传至此目录或此目录下的子目录的新文件将会使用绑定的存储策略存储。复制、移动到此目录不会应用绑定的存储策略;多个父目录指定存储策略时将会选择最接近的父目录的存储策略。",
|
||||
"mountNewFolder": "绑定新目录",
|
||||
"nsfw": "色情信息",
|
||||
"malware": "包含病毒",
|
||||
"copyright": "侵权",
|
||||
"inappropriateStatements": "不恰当的言论",
|
||||
"other": "其他",
|
||||
"groupBaseQuota": "用户组基础容量",
|
||||
"validPackQuota": "有效容量包附加容量",
|
||||
"used": "已使用容量",
|
||||
"total": "总容量",
|
||||
"validStoragePack": "可用容量包",
|
||||
"buyStoragePack": "购买容量包",
|
||||
"useGiftCode": "使用激活码兑换",
|
||||
"packName": "容量包名称",
|
||||
"activationDate": "激活日期",
|
||||
"validDuration": "有效期",
|
||||
"expiredAt": "过期日期",
|
||||
"days": "{{num}} 天",
|
||||
"pleaseInputGiftCode": "请输入激活码",
|
||||
"pleaseSelectAStoragePack": "请先选择一个容量包",
|
||||
"selectPaymentMethod": "选择支付方式:",
|
||||
"noAvailableMethod": "无可用支付方式",
|
||||
"alipay": "支付宝扫码",
|
||||
"wechatPay": "微信扫码",
|
||||
"payByCredits": "积分支付",
|
||||
"purchaseDuration": "购买时长倍数:",
|
||||
"creditsNum": "充值积分数量:",
|
||||
"store": "商店",
|
||||
"storagePacks": "容量包",
|
||||
"membership": "会员",
|
||||
"buyCredits": "积分充值",
|
||||
"subtotal": "当前费用:",
|
||||
"creditsTotalNum": "{{num}} 积分",
|
||||
"checkoutNow": "立即购买",
|
||||
"recommended": "推荐",
|
||||
"enterGiftCode": "输入激活码",
|
||||
"qrcodeAlipay": "请使用 支付宝 扫描下方二维码完成付款,付款完成后本页面会自动刷新。",
|
||||
"qrcodeWechat": "请使用 微信 扫描下方二维码完成付款,付款完成后本页面会自动刷新。",
|
||||
"qrcodeCustom": "请扫描下方二维码完成付款,付款完成后本页面会自动刷新。",
|
||||
"paymentCompleted": "支付完成",
|
||||
"productDelivered": "您所购买的商品已到账。",
|
||||
"confirmRedeem": "确认兑换",
|
||||
"productName": "商品名称:",
|
||||
"qyt": "数量:",
|
||||
"duration": "时长:",
|
||||
"subscribe": "购买用户组",
|
||||
"selected": "已选:",
|
||||
"paymentQrcode": "付款二维码",
|
||||
"validDurationDays": "有效期:{{num}} 天",
|
||||
"reportSuccessful": "举报成功",
|
||||
"additionalDescription": "补充描述",
|
||||
"announcement": "公告",
|
||||
"dontShowAgain": "不再显示",
|
||||
"openPaymentLink": "直接打开支付链接"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,5 +74,17 @@
|
|||
"50005": "內部错误 ({{message}})",
|
||||
"50010": "目标节点不可用",
|
||||
"50011": "文件元信息查询失败"
|
||||
},
|
||||
"vasErrors": {
|
||||
"40031": "此 Email 服务提供商不可用,请更换其他 Email 地址",
|
||||
"40059": "不能转存自己的分享",
|
||||
"40062": "积分不足",
|
||||
"40063": "当前用户组仍未过期,请前往个人设置手动解约后继续",
|
||||
"40064": "您当前已处于此用户组中",
|
||||
"40065": "兑换码无效",
|
||||
"40066": "您已绑定了QQ账号,请先解除绑定",
|
||||
"40067": "此QQ账号已被绑定其他账号",
|
||||
"40068": "此QQ号未绑定任何账号",
|
||||
"40072": "管理员无法升级至其他用户组"
|
||||
}
|
||||
}
|
||||
|
|
@ -49,13 +49,14 @@
|
|||
"totalFiles": "文件总数",
|
||||
"publicShares": "公开分享总数",
|
||||
"privateShares": "私密分享总数",
|
||||
"homepage": "主页",
|
||||
"documents": "文档",
|
||||
"homepage": "官方网站",
|
||||
"documents": "使用文档",
|
||||
"github": "Github 仓库",
|
||||
"forum": "讨论社区",
|
||||
"forumLink": "https://forum.cloudreve.org",
|
||||
"telegramGroup": "Telegram 群组",
|
||||
"telegramGroupLink": "https://t.me/cloudreve_official",
|
||||
"buyPro": "购买捐助版",
|
||||
"buyPro": "捐助开发者",
|
||||
"publishedAt": "发表于 <0></0>",
|
||||
"newsTag": "notice"
|
||||
},
|
||||
|
|
@ -67,12 +68,17 @@
|
|||
"mainTitleDes": "站点的主标题",
|
||||
"subTitle": "副标题",
|
||||
"subTitleDes": "站点的副标题",
|
||||
"siteKeywords": "关键词",
|
||||
"siteKeywordsDes": "站点关键词,以英文逗号分隔",
|
||||
"siteDescription": "站点描述",
|
||||
"siteDescriptionDes": "站点描述信息,可能会在分享页面摘要内展示",
|
||||
"siteURL": "站点 URL",
|
||||
"siteURLDes": "非常重要,请确保与实际情况一致。使用云存储策略、支付平台时,请填入可以被外网访问的地址",
|
||||
"customFooterHTML": "页脚代码",
|
||||
"customFooterHTMLDes": "在页面底部插入的自定义 HTML 代码",
|
||||
"announcement": "站点公告",
|
||||
"announcementDes": "展示给已登陆用户的公告,留空不展示。当此项内容更改时,所有用户会重新看到公告",
|
||||
"supportHTML": "支持 HTML 代码",
|
||||
"pwa": "渐进式应用 (PWA)",
|
||||
"smallIcon": "小图标",
|
||||
"smallIconDes": "扩展名为 ico 的小图标地址",
|
||||
|
|
@ -213,6 +219,7 @@
|
|||
"officePreviewServiceSrcB64Des": " Base64 编码后的文件 URL",
|
||||
"officePreviewServiceName": "文件名",
|
||||
"thumbnails": "缩略图",
|
||||
"localOnlyInfo": "以下设置只针对本机存储策略有效。",
|
||||
"thumbnailDoc": "有关配置缩略图的更多信息,请参阅 <0>官方文档</0>。",
|
||||
"thumbnailDocLink":"https://docs.cloudreve.org/use/thumbnails",
|
||||
"thumbnailBasic": "基本设置",
|
||||
|
|
@ -618,7 +625,7 @@
|
|||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"slaveNodeDes": "您可以添加同样运行了 Cloudreve 的服务器作为从机端,正常运行工作的从机端可以为主机分担某些异步任务(如离线下载)。请参考下面向导部署并配置连接 Cloudreve 从机节点。<0>如果你已经在目标服务器上部署了从机存储策略,您可以跳过本页面的某些步骤,只将从机密钥、服务器地址在这里填写并保持与从机存储策略中一致即可。</0> 在后续版本中,从机存储策略的相关配置会合并到这里。",
|
||||
"overwriteDes": "; 以下为可选的设置,对应主机节点的相关参数,可以通过配置文件应用到从机节点,请根据<0></0>; 实际情况调整。更改下面设置需要重启从机节点后生效。",
|
||||
"overwriteDes": "; 以下为可选的设置,对应主机节点的相关参数,可以通过配置文件应用到从机节点,请根据<0></0>; 实际情况调整。更改下面设置需要重启从机节点后生效。",
|
||||
"workerNumDes": "任务队列最多并行执行的任务数",
|
||||
"parallelTransferDes": "任务队列中转任务传输时,最大并行协程数",
|
||||
"chunkRetriesDes": "中转分片上传失败后重试的最大次数",
|
||||
|
|
@ -688,6 +695,8 @@
|
|||
"nameOfGroupDes": "用户组的名称",
|
||||
"storagePolicy": "存储策略",
|
||||
"storageDes": "指定用户组的存储策略。",
|
||||
"availablePolicies": "可用存储策略",
|
||||
"availablePoliciesDes": "指定用户组可用的存储策略,可多选,用户可在选定范围内自由切换存储策略。",
|
||||
"initialStorageQuota": "初始容量",
|
||||
"initialStorageQuotaDes": "用户组下的用户初始可用最大容量",
|
||||
"downloadSpeedLimit": "下载限速",
|
||||
|
|
@ -718,8 +727,14 @@
|
|||
"compressSizeDes": "用户可创建的压缩任务的文件最大总大小,填写为 0 表示不限制",
|
||||
"decompressSize": "待解压文件最大大小",
|
||||
"decompressSizeDes": "用户可创建的解压缩任务的文件最大总大小,填写为 0 表示不限制",
|
||||
"migratePolicy": "存储策略转移",
|
||||
"migratePolicyDes": "是否用户创建存储策略转移任务",
|
||||
"allowSelectNode": "允许选择下载节点",
|
||||
"allowSelectNodeDes": "开启后,用户可以在创建任务前选择处理下载的从机节点;关闭后,系统会自动分配节点。",
|
||||
"redirectedSource": "使用重定向的外链",
|
||||
"redirectedSourceDes": "开启后,用户获取的文件外链将由 Cloudreve 中转,链接较短。关闭后,用户获取的文件外链会变成文件的原始链接。部分存储策略获取的非中转外链无法保持永久有效,请参阅 <0>比较存储策略</0>。",
|
||||
"availableNodes": "可用下载节点",
|
||||
"availableNodesDes": "指定用户组可用的下载节点,留空表示全部节点都可用。用户只能在此列表内选择或被负载均衡分配下载节点。",
|
||||
"advanceDelete": "允许使用高级文件删除选项",
|
||||
"advanceDeleteDes": "开启后,用户在前台删除文件时可以选择是否强制删除、是否仅解除物理链接。这些选项与后台管理面板删除文件时类似,请只开放给可信用户组。"
|
||||
},
|
||||
|
|
@ -821,5 +836,151 @@
|
|||
"editGroupDes": "当你添加多个可用于离线下载的节点后,主节点会将离线下载请求轮流发送到这些节点处理。节点离线下载配置完成后,您可能还需要 <0>到这里</0> 编辑用户组,为对应用户组开启离线下载权限。",
|
||||
"lastProgress": "最后进度",
|
||||
"errorMsg": "错误信息"
|
||||
},
|
||||
"vas": {
|
||||
"vas": "增值服务",
|
||||
"reports": "举报",
|
||||
"orders": "订单",
|
||||
"initialFiles": "初始文件",
|
||||
"filterEmailProvider": "邮箱过滤",
|
||||
"filterEmailProviderWhitelist": "白名单",
|
||||
"initialFilesDes": "指定用户注册后初始拥有的文件。输入文件 ID 搜索并添加现有文件。",
|
||||
"filterEmailProviderDisabled": "不启用",
|
||||
"filterEmailProviderDes": "过滤注册邮箱域",
|
||||
"appLinkDes": "用于在 App 设置页面展示,留空即不展示链接按钮,仅当 VOL 授权有效时此项设置才会生效。",
|
||||
"filterEmailProviderBlacklist": "黑名单",
|
||||
"filterEmailProviderRuleDes": "多个域请使用半角逗号隔开",
|
||||
"filterEmailProviderRule": "邮箱域过滤规则",
|
||||
"qqConnectHint": "创建应用时,回调地址请填写:{{url}}",
|
||||
"enableQQConnectDes": "是否允许绑定QQ、使用QQ登录本站",
|
||||
"loginWithoutBindingDes": "开启后,如果用户使用了QQ登录,但是没有已绑定的注册用户,系统会为其创建用户并登录。这种方式创建的用户日后只能使用QQ登录。",
|
||||
"qqConnect": "QQ互联",
|
||||
"enableQQConnect": "开启QQ互联",
|
||||
"appidDes": "应用管理页面获取到的的 APP ID",
|
||||
"appForum": "用户论坛 URL",
|
||||
"loginWithoutBinding": "未绑定时可直接登录",
|
||||
"appKeyDes": "应用管理页面获取到的的 APP KEY",
|
||||
"appid": "APP ID",
|
||||
"overuseReminderDes": "用户因增值服务过期,容量超出限制后发送的提醒邮件模板",
|
||||
"storagePack": "容量包",
|
||||
"giftCodes": "兑换码",
|
||||
"appKey": "APP KEY",
|
||||
"overuseReminder": "超额提醒",
|
||||
"enable": "开启",
|
||||
"appFeedback": "反馈页面 URL",
|
||||
"vasSetting": "支付/杂项设置",
|
||||
"appIDDes": "当面付应用的 APPID",
|
||||
"purchasableGroups": "可购用户组",
|
||||
"rsaPrivateDes": "当面付应用的 RSA2 (SHA256) 私钥,一般是由您自己生成。详情参考 <0>生成 RSA 密钥</0>。",
|
||||
"alipayPublicKeyDes": "由支付宝提供,可在 应用管理 - 应用信息 - 接口加签方式 中获取。",
|
||||
"applicationID": "应用 ID",
|
||||
"alipay": "支付宝当面付",
|
||||
"appID": "App- ID",
|
||||
"merchantID": "直连商户号",
|
||||
"customPaymentEndpointDes":"创建支付订单时请求的接口 URL",
|
||||
"rsaPrivate": "RSA 应用私钥",
|
||||
"apiV3Secret": "API v3 密钥",
|
||||
"alipayPublicKey": "支付宝公钥",
|
||||
"mcCertificateSerial": "商户证书序列号",
|
||||
"mcAPISecret": "商户API 私钥",
|
||||
"payjs": "PAYJS 微信支付",
|
||||
"wechatPay": "微信官方扫码支付",
|
||||
"applicationIDDes": "直连商户申请的公众号或移动应用appid",
|
||||
"mcNumber": "商户号",
|
||||
"customPaymentEndpoint":"支付接口地址",
|
||||
"merchantIDDes": "直连商户的商户号,由微信支付生成并下发。",
|
||||
"communicationSecret": "通信密钥",
|
||||
"apiV3SecretDes": "商户需先在【商户平台】-【API安全】的页面设置该密钥,请求才能通过微信支付的签名校验。密钥的长度为 32 个字节。",
|
||||
"banBufferPeriod": "封禁缓冲期 (秒)",
|
||||
"allowSellShares": "允许为分享定价",
|
||||
"creditPriceRatio": "积分到账比率 (%)",
|
||||
"mcCertificateSerialDes": "登录商户平台【API安全】-【API证书】-【查看证书】,可查看商户 API 证书序列号。",
|
||||
"mcAPISecretDes": "私钥文件 apiclient_key.pem 的内容。",
|
||||
"creditPrice": "积分价格 (分)",
|
||||
"customPaymentSecretDes": "Cloudreve 用于签名付款请求的密钥",
|
||||
"payjsWarning": "此服务由第三方平台 <0>PAYJS</0> 提供, 产生的任何纠纷与 Cloudreve 开发者无关。",
|
||||
"add": "添加",
|
||||
"mcNumberDes": "可在 PAYJS 管理面板首页看到",
|
||||
"price": "单价",
|
||||
"size": "大小",
|
||||
"orCredits": " 或 {{num}} 积分",
|
||||
"otherSettings": "杂项设置",
|
||||
"banBufferPeriodDes": "用户保持容量超额状态的最长时长,超出时长该用户会被系统冻结。",
|
||||
"yes": "是",
|
||||
"customPaymentNameDes": "用于展示给用户的付款方式名称",
|
||||
"allowSellSharesDes": "开启后,用户可为分享设定积分价格,下载需要扣除积分。",
|
||||
"productName": "商品名",
|
||||
"creditPriceRatioDes": "购买下载设定价格的分享,分享者实际到账的积分比率。",
|
||||
"code": "兑换码",
|
||||
"invalidProduct": "已失效商品",
|
||||
"notUsed": "未使用",
|
||||
"creditPriceDes": "充值积分时的价格",
|
||||
"allowReportShare": "允许举报分享",
|
||||
"allowReportShareDes": "开启后,任意用户可对分享进行举报,有被刷数据库的风险",
|
||||
"name": "名称",
|
||||
"addStoragePack": "添加容量包",
|
||||
"customPaymentName": "付款方式名称",
|
||||
"duration": "时长",
|
||||
"productNameDes": "商品展示名称",
|
||||
"actions": "操作",
|
||||
"durationDay": "有效期 (天)",
|
||||
"priceYuan": "单价 (元)",
|
||||
"priceCredits": "单价 (积分)",
|
||||
"highlight": "突出展示",
|
||||
"no": "否",
|
||||
"editMembership": "编辑可购用户组",
|
||||
"customPaymentDocumentLink":"https://docs.cloudreve.org/use/pro/pay",
|
||||
"qyt": "数量",
|
||||
"group": "用户组",
|
||||
"status": "状态",
|
||||
"durationGroupDes": "购买后升级的用户组单位购买时间的有效期",
|
||||
"productDescription": "商品描述 (一行一个)",
|
||||
"highlightDes": "开启后,在商品选择页面会被突出展示",
|
||||
"used": "已使用",
|
||||
"generatingResult": "生成结果",
|
||||
"numberOfCodes": "生成数量",
|
||||
"customPaymentDes":"通过实现 Cloudreve 兼容付款接口来对接其他第三方支付平台,详情请参考 <0>官方文档</0>。",
|
||||
"editStoragePack": "编辑容量包",
|
||||
"linkedProduct": "对应商品",
|
||||
"packSizeDes": "容量包的大小",
|
||||
"productQytDes": "对于积分类商品,此处为积分数量,其他商品为时长倍数",
|
||||
"freeDownloadDes": "开启后,用户可以免费下载需付积分的分享",
|
||||
"markSuccessful": "标记成功",
|
||||
"durationDayDes": "每个容量包的有效期",
|
||||
"packPriceDes": "容量包的单价",
|
||||
"reportedContent": "举报对象",
|
||||
"customPayment": "自定义付款渠道",
|
||||
"priceCreditsDes": "使用积分购买时的价格,填写为 0 表示不能使用积分购买",
|
||||
"description": "补充描述",
|
||||
"addMembership": "添加可购用户组",
|
||||
"invalid": "[已失效]",
|
||||
"orderDeleted": "订单记录已删除",
|
||||
"product": "商品",
|
||||
"groupDes": "购买后升级的用户组",
|
||||
"groupPriceDes": "用户组的单价",
|
||||
"paidBy": "支付方式",
|
||||
"showAppPromotion": "展示客户端引导页面",
|
||||
"showAppPromotionDes": "开启后,用户可以在 “连接与挂载” 页面中看到移动客户端的使用引导",
|
||||
"productDescriptionDes": "购买页面展示的商品描述",
|
||||
"unpaid": "未支付",
|
||||
"generateGiftCode": "生成兑换码",
|
||||
"shareLink": "分享链接",
|
||||
"iosVol": "iOS 客户端批量授权 (VOL)",
|
||||
"syncLicense": "同步授权",
|
||||
"numberOfCodesDes": "激活码批量生成数量",
|
||||
"productQyt": "商品数量",
|
||||
"freeDownload": "免积分下载分享",
|
||||
"credits": "积分",
|
||||
"markAsResolved": "标记为已处理",
|
||||
"reason": "原因",
|
||||
"reportTime": "举报时间",
|
||||
"deleteShare": "删除分享",
|
||||
"orderName": "订单名",
|
||||
"orderNumber": "订单号",
|
||||
"orderOwner": "创建者",
|
||||
"paid": "已支付",
|
||||
"volPurchase": "客户端 VOL 授权需要单独在 <0>授权管理面板</0> 购买。VOL 授权允许您的用户免费使用 <1>Cloudreve iOS 客户端</1> 连接到您的站点,无需用户再付费订阅 iOS 客户端。购买授权后请点击下方同步授权。",
|
||||
"mobileApp": "移动客户端",
|
||||
"volSynced": "VOL 授权已同步"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -498,5 +498,92 @@
|
|||
"aboutCloudreve": "關於 Cloudreve",
|
||||
"githubRepo": "GitHub 倉庫",
|
||||
"homepage": "主頁"
|
||||
},
|
||||
"vas": {
|
||||
"loginWithQQ": "使用 QQ 登錄",
|
||||
"quota": "容量配額",
|
||||
"exceedQuota": "您的已用容量已超過容量配額,請儘快刪除多餘文件或購買容量",
|
||||
"extendStorage": "擴容",
|
||||
"folderPolicySwitched": "目錄存儲策略已切換",
|
||||
"switchFolderPolicy": "切換目錄存儲策略",
|
||||
"setPolicyForFolder": "爲當前目錄設置存儲策略: ",
|
||||
"manageMount": "管理綁定",
|
||||
"saveToMyFiles": "保存到我的文件",
|
||||
"report": "舉報",
|
||||
"migrateStoragePolicy": "轉移存儲策略",
|
||||
"fileSaved": "文件已保存",
|
||||
"sharePurchaseTitle": "確定要支付 {{score}} 積分 購買此分享?",
|
||||
"sharePurchaseDescription": "購買後,您可以自由預覽、下載此分享的所有內容,一定期限內不會重複扣費。如果您已購買,請忽略此提示。",
|
||||
"payToDownload": "付積分下載",
|
||||
"creditToBePaid": "每人次下載需支付的積分",
|
||||
"creditGainPredict": "預計每人次下載可到賬 {{num}} 積分",
|
||||
"creditPrice": " ({{num}} 積分)",
|
||||
"creditFree": " (免積分)",
|
||||
"cancelSubscription": "解約成功,更改會在數分鐘後生效",
|
||||
"qqUnlinked": "已解除與QQ賬戶的關聯",
|
||||
"groupExpire": " <0></0> 過期",
|
||||
"manuallyCancelSubscription": "手動解約當前用戶組",
|
||||
"qqAccount": "QQ賬號",
|
||||
"connect": "綁定",
|
||||
"unlink": "解除綁定",
|
||||
"credits": "積分",
|
||||
"cancelSubscriptionTitle": "解約用戶組",
|
||||
"cancelSubscriptionWarning": "將要退回到初始用戶組,且所支付金額無法退還,確定要繼續嗎?",
|
||||
"mountPolicy": "存儲策略綁定",
|
||||
"mountDescription": "爲目錄綁定存儲策略後,上傳至此目錄或此目錄下的子目錄的新文件將會使用綁定的存儲策略存儲。複製、移動到此目錄不會應用綁定的存儲策略;多個父目錄指定存儲策略時將會選擇最接近的父目錄的存儲策略。",
|
||||
"mountNewFolder": "綁定新目錄",
|
||||
"nsfw": "色情信息",
|
||||
"malware": "包含病毒",
|
||||
"copyright": "侵權",
|
||||
"inappropriateStatements": "不恰當的言論",
|
||||
"other": "其他",
|
||||
"groupBaseQuota": "用戶組基礎容量",
|
||||
"validPackQuota": "有效容量包附加容量",
|
||||
"used": "已使用容量",
|
||||
"total": "總容量",
|
||||
"validStoragePack": "可用容量包",
|
||||
"buyStoragePack": "購買容量包",
|
||||
"useGiftCode": "使用激活碼兌換",
|
||||
"packName": "容量包名稱",
|
||||
"activationDate": "激活日期",
|
||||
"validDuration": "有效期",
|
||||
"expiredAt": "過期日期",
|
||||
"days": "{{num}} 天",
|
||||
"pleaseInputGiftCode": "請輸入激活碼",
|
||||
"pleaseSelectAStoragePack": "請先選擇一個容量包",
|
||||
"selectPaymentMethod": "選擇支付方式:",
|
||||
"noAvailableMethod": "無可用支付方式",
|
||||
"alipay": "支付寶掃碼",
|
||||
"wechatPay": "微信掃碼",
|
||||
"payByCredits": "積分支付",
|
||||
"purchaseDuration": "購買時長倍數:",
|
||||
"creditsNum": "充值積分數量:",
|
||||
"store": "商店",
|
||||
"storagePacks": "容量包",
|
||||
"membership": "會員",
|
||||
"buyCredits": "積分充值",
|
||||
"subtotal": "當前費用:",
|
||||
"creditsTotalNum": "{{num}} 積分",
|
||||
"checkoutNow": "立即購買",
|
||||
"recommended": "推薦",
|
||||
"enterGiftCode": "輸入激活碼",
|
||||
"qrcodeAlipay": "請使用 支付寶 掃描下方二維碼完成付款,付款完成後本頁面會自動刷新。",
|
||||
"qrcodeWechat": "請使用 微信 掃描下方二維碼完成付款,付款完成後本頁面會自動刷新。",
|
||||
"qrcodeCustom": "請掃描下方二維碼完成付款,付款完成後本頁面會自動刷新。",
|
||||
"paymentCompleted": "支付完成",
|
||||
"productDelivered": "您所購買的商品已到賬。",
|
||||
"confirmRedeem": "確認兌換",
|
||||
"productName": "商品名稱:",
|
||||
"qyt": "數量:",
|
||||
"duration": "時長:",
|
||||
"subscribe": "購買用戶組",
|
||||
"selected": "已選:",
|
||||
"paymentQrcode": "付款二維碼",
|
||||
"validDurationDays": "有效期:{{num}} 天",
|
||||
"reportSuccessful": "舉報成功",
|
||||
"additionalDescription": "補充描述",
|
||||
"announcement": "公告",
|
||||
"dontShowAgain": "不再顯示",
|
||||
"openPaymentLink": "直接打開支付鏈接"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
"40002": "上傳失敗",
|
||||
"40003": "目錄建立失敗",
|
||||
"40004": "同名對象已存在",
|
||||
"40004": "同名對象已存在",
|
||||
"40005": "簽名過期",
|
||||
"40006": "不支援的儲存策略類型",
|
||||
"40007": "當前用戶組無法進行此操作",
|
||||
|
|
@ -75,5 +74,17 @@
|
|||
"50005": "內部錯誤 ({{message}})",
|
||||
"50010": "目標節點不可用",
|
||||
"50011": "文件元訊息查詢失敗"
|
||||
},
|
||||
"vasErrors": {
|
||||
"40031": "此 Email 服務提供商不可用,請更換其他 Email 地址",
|
||||
"40059": "不能轉存自己的分享",
|
||||
"40062": "積分不足",
|
||||
"40063": "當前用戶組仍未過期,請前往個人設置手動解約後繼續",
|
||||
"40064": "您當前已處於此用戶組中",
|
||||
"40065": "兌換碼無效",
|
||||
"40066": "您已綁定了QQ賬號,請先解除綁定",
|
||||
"40067": "此QQ賬號已被綁定其他賬號",
|
||||
"40068": "此QQ號未綁定任何賬號",
|
||||
"40072": "管理員無法升級至其他用戶組"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,9 @@
|
|||
"totalFiles": "文件總數",
|
||||
"publicShares": "公開分享總數",
|
||||
"privateShares": "私密分享總數",
|
||||
"homepage": "首頁",
|
||||
"documents": "文件",
|
||||
"homepage": "官方網站",
|
||||
"documents": "用戶指南",
|
||||
"github": "Github 倉庫",
|
||||
"forum": "討論社群",
|
||||
"forumLink": "https://forum.cloudreve.org",
|
||||
"telegramGroup": "Telegram 群組",
|
||||
|
|
@ -67,12 +68,16 @@
|
|||
"mainTitleDes": "站點的主標題",
|
||||
"subTitle": "副標題",
|
||||
"subTitleDes": "站點的副標題",
|
||||
"siteKeywords": "關鍵字",
|
||||
"siteKeywordsDes": "網站關鍵字,以英文逗號分隔",
|
||||
"siteDescription": "站點描述",
|
||||
"siteDescriptionDes": "站點描述訊息,可能會在分享頁面摘要內展示",
|
||||
"siteURL": "站點 URL",
|
||||
"siteURLDes": "非常重要,請確保與實際情況一致。使用雲端儲存策略、支付平台時,請填入可以被外網訪問的地址",
|
||||
"customFooterHTML": "頁尾程式碼",
|
||||
"customFooterHTMLDes": "在頁面底部插入的自訂 HTML 程式碼",
|
||||
"announcement": "站點公告",
|
||||
"announcementDes": "展示給已登入使用者的公告,留空不展示。當此項目內容變更時,所有使用者會重新看到公告",
|
||||
"pwa": "漸進式應用 (PWA)",
|
||||
"smallIcon": "小圖示",
|
||||
"smallIconDes": "副檔名為 ico 的小圖示地址",
|
||||
|
|
@ -213,6 +218,7 @@
|
|||
"officePreviewServiceSrcB64Des": " Base64 編碼後的文件 URL",
|
||||
"officePreviewServiceName": "檔案名",
|
||||
"thumbnails": "縮圖",
|
||||
"localOnlyInfo": "以下設置只針對本機儲存策略有效。",
|
||||
"thumbnailDoc": "有關配置縮圖的更多信息,請參閱 <0>官方文件</0>。",
|
||||
"thumbnailDocLink":"https://docs.cloudreve.org/use/thumbnails",
|
||||
"thumbnailBasic": "基本設定",
|
||||
|
|
@ -821,5 +827,151 @@
|
|||
"editGroupDes": "當你新增多個可用於離線下載的節點後,主節點會將離線下載請求輪流發送到這些節點處理。節點離線下載配置完成後,您可能還需要 <0>到這裡</0> 編輯用戶組,為對應用戶組開啟離線下載權限。",
|
||||
"lastProgress": "最後進度",
|
||||
"errorMsg": "錯誤訊息"
|
||||
},
|
||||
"vas": {
|
||||
"vas": "增值服務",
|
||||
"reports": "舉報",
|
||||
"orders": "訂單",
|
||||
"initialFiles": "初始文件",
|
||||
"initialFilesDes": "指定用戶註冊後初始擁有的文件。輸入文件 ID 搜索並添加現有文件。",
|
||||
"filterEmailProvider": "郵箱過濾",
|
||||
"filterEmailProviderDisabled": "不啓用",
|
||||
"filterEmailProviderWhitelist": "白名單",
|
||||
"filterEmailProviderBlacklist": "黑名單",
|
||||
"filterEmailProviderDes": "過濾註冊郵箱域",
|
||||
"filterEmailProviderRule": "郵箱域過濾規則",
|
||||
"filterEmailProviderRuleDes": "多個域請使用半角逗號隔開",
|
||||
"qqConnect": "QQ互聯",
|
||||
"qqConnectHint": "創建應用時,回調地址請填寫:{{url}}",
|
||||
"enableQQConnect": "開啓QQ互聯",
|
||||
"enableQQConnectDes": "是否允許綁定QQ、使用QQ登錄本站",
|
||||
"loginWithoutBinding": "未綁定時可直接登錄",
|
||||
"loginWithoutBindingDes": "開啓後,如果用戶使用了QQ登錄,但是沒有已綁定的註冊用戶,系統會爲其創建用戶並登錄。這種方式創建的用戶日後只能使用QQ登錄。",
|
||||
"appid": "APP ID",
|
||||
"appidDes": "應用管理頁面獲取到的的 APP ID",
|
||||
"appKey": "APP KEY",
|
||||
"appKeyDes": "應用管理頁面獲取到的的 APP KEY",
|
||||
"overuseReminder": "超額提醒",
|
||||
"overuseReminderDes": "用戶因增值服務過期,容量超出限制後發送的提醒郵件模板",
|
||||
"vasSetting": "支付/雜項設置",
|
||||
"storagePack": "容量包",
|
||||
"purchasableGroups": "可購用戶組",
|
||||
"giftCodes": "兌換碼",
|
||||
"alipay": "支付寶當面付",
|
||||
"enable": "開啓",
|
||||
"appID": "App- ID",
|
||||
"appIDDes": "當面付應用的 APPID",
|
||||
"rsaPrivate": "RSA 應用私鑰",
|
||||
"rsaPrivateDes": "當面付應用的 RSA2 (SHA256) 私鑰,一般是由您自己生成。詳情參考 <0>生成 RSA 密鑰</0>。",
|
||||
"alipayPublicKey": "支付寶公鑰",
|
||||
"alipayPublicKeyDes": "由支付寶提供,可在 應用管理 - 應用信息 - 接口加簽方式 中獲取。",
|
||||
"wechatPay": "微信官方掃碼支付",
|
||||
"applicationID": "應用 ID",
|
||||
"applicationIDDes": "直連商戶申請的公衆號或移動應用appid",
|
||||
"merchantID": "直連商戶號",
|
||||
"merchantIDDes": "直連商戶的商戶號,由微信支付生成並下發。",
|
||||
"apiV3Secret": "API v3 密鑰",
|
||||
"apiV3SecretDes": "商戶需先在【商戶平臺】-【API安全】的頁面設置該密鑰,請求才能通過微信支付的簽名校驗。密鑰的長度爲 32 個字節。",
|
||||
"mcCertificateSerial": "商戶證書序列號",
|
||||
"mcCertificateSerialDes": "登錄商戶平臺【API安全】-【API證書】-【查看證書】,可查看商戶 API 證書序列號。",
|
||||
"mcAPISecret": "商戶API 私鑰",
|
||||
"mcAPISecretDes": "私鑰文件 apiclient_key.pem 的內容。",
|
||||
"payjs": "PAYJS 微信支付",
|
||||
"payjsWarning": "此服務由第三方平臺 <0>PAYJS</0> 提供, 產生的任何糾紛與 Cloudreve 開發者無關。",
|
||||
"mcNumber": "商戶號",
|
||||
"mcNumberDes": "可在 PAYJS 管理面板首頁看到",
|
||||
"communicationSecret": "通信密鑰",
|
||||
"otherSettings": "雜項設置",
|
||||
"banBufferPeriod": "封禁緩衝期 (秒)",
|
||||
"banBufferPeriodDes": "用戶保持容量超額狀態的最長時長,超出時長該用戶會被系統凍結。",
|
||||
"allowSellShares": "允許爲分享定價",
|
||||
"allowSellSharesDes": "開啓後,用戶可爲分享設定積分價格,下載需要扣除積分。",
|
||||
"creditPriceRatio": "積分到賬比率 (%)",
|
||||
"creditPriceRatioDes": "購買下載設定價格的分享,分享者實際到賬的積分比率。",
|
||||
"creditPrice": "積分價格 (分)",
|
||||
"creditPriceDes": "充值積分時的價格",
|
||||
"allowReportShare": "允許舉報分享",
|
||||
"allowReportShareDes": "開啟後,任意用戶可對分享進行檢舉,有資料庫被填滿的風險",
|
||||
"add": "添加",
|
||||
"name": "名稱",
|
||||
"price": "單價",
|
||||
"duration": "時長",
|
||||
"size": "大小",
|
||||
"actions": "操作",
|
||||
"orCredits": " 或 {{num}} 積分",
|
||||
"highlight": "突出展示",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"productName": "商品名",
|
||||
"qyt": "數量",
|
||||
"code": "兌換碼",
|
||||
"status": "狀態",
|
||||
"invalidProduct": "已失效商品",
|
||||
"used": "已使用",
|
||||
"notUsed": "未使用",
|
||||
"generatingResult": "生成結果",
|
||||
"addStoragePack": "添加容量包",
|
||||
"editStoragePack": "編輯容量包",
|
||||
"productNameDes": "商品展示名稱",
|
||||
"packSizeDes": "容量包的大小",
|
||||
"durationDay": "有效期 (天)",
|
||||
"durationDayDes": "每個容量包的有效期",
|
||||
"priceYuan": "單價 (元)",
|
||||
"packPriceDes": "容量包的單價",
|
||||
"priceCredits": "單價 (積分)",
|
||||
"priceCreditsDes": "使用積分購買時的價格,填寫爲 0 表示不能使用積分購買",
|
||||
"editMembership": "編輯可購用戶組",
|
||||
"addMembership": "添加可購用戶組",
|
||||
"group": "用戶組",
|
||||
"groupDes": "購買後升級的用戶組",
|
||||
"durationGroupDes": "購買後升級的用戶組單位購買時間的有效期",
|
||||
"groupPriceDes": "用戶組的單價",
|
||||
"productDescription": "商品描述 (一行一個)",
|
||||
"productDescriptionDes": "購買頁面展示的商品描述",
|
||||
"highlightDes": "開啓後,在商品選擇頁面會被突出展示",
|
||||
"generateGiftCode": "生成兌換碼",
|
||||
"numberOfCodes": "生成數量",
|
||||
"numberOfCodesDes": "激活碼批量生成數量",
|
||||
"linkedProduct": "對應商品",
|
||||
"productQyt": "商品數量",
|
||||
"productQytDes": "對於積分類商品,此處爲積分數量,其他商品爲時長倍數",
|
||||
"freeDownload": "免積分下載分享",
|
||||
"freeDownloadDes": "開啓後,用戶可以免費下載需付積分的分享",
|
||||
"credits": "積分",
|
||||
"markSuccessful": "標記成功",
|
||||
"markAsResolved": "標記爲已處理",
|
||||
"reportedContent": "舉報對象",
|
||||
"reason": "原因",
|
||||
"description": "補充描述",
|
||||
"reportTime": "舉報時間",
|
||||
"invalid": "[已失效]",
|
||||
"deleteShare": "刪除分享",
|
||||
"orderDeleted": "訂單記錄已刪除",
|
||||
"orderName": "訂單名",
|
||||
"product": "商品",
|
||||
"orderNumber": "訂單號",
|
||||
"paidBy": "支付方式",
|
||||
"orderOwner": "創建者",
|
||||
"unpaid": "未支付",
|
||||
"paid": "已支付",
|
||||
"shareLink": "分享鏈接",
|
||||
"volPurchase": "客戶端 VOL 授權需要單獨在 <0>授權管理面板</0> 購買。VOL 授權允許您的用戶免費使用 <1>Cloudreve iOS 客戶端</1> 連接到您的站點,無需用戶再付費訂閱 iOS 客戶端。購買授權後請點擊下方同步授權。",
|
||||
"iosVol": "iOS 客戶端批量授權 (VOL)",
|
||||
"mobileApp": "移動客戶端",
|
||||
"syncLicense": "同步授權",
|
||||
"volSynced": "VOL 授權已同步",
|
||||
"showAppPromotion": "展示客戶端引導頁面",
|
||||
"showAppPromotionDes": "開啓後,用戶可以在 “連接與掛載” 頁面中看到移動客戶端的使用引導",
|
||||
"customPayment": "自定義付款渠道",
|
||||
"customPaymentDes":"通過實現 Cloudreve 兼容付款接口來對接其他第三方支付平臺,詳情請參考 <0>官方文檔</0>。",
|
||||
"customPaymentDocumentLink":"https://docs.cloudreve.org/use/pro/pay",
|
||||
"customPaymentName": "付款方式名稱",
|
||||
"customPaymentNameDes": "用於展示給用戶的付款方式名稱",
|
||||
"customPaymentSecretDes": "Cloudreve 用於簽名付款請求的密鑰",
|
||||
"customPaymentEndpoint":"支付接口地址",
|
||||
"customPaymentEndpointDes":"創建支付訂單時請求的接口 URL",
|
||||
"appFeedback": "反饋頁面 URL",
|
||||
"appForum": "用戶論壇 URL",
|
||||
"appLinkDes": "用於在 App 設置頁面展示,留空即不展示鏈接按鈕,僅當 VOL 授權有效時此項設置纔會生效。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
src/Admin.js
16
src/Admin.js
|
|
@ -8,12 +8,12 @@ import { Route, Switch } from "react-router-dom";
|
|||
import { ThemeProvider } from "@material-ui/styles";
|
||||
import createTheme from "@material-ui/core/styles/createMuiTheme";
|
||||
import { zhCN } from "@material-ui/core/locale";
|
||||
|
||||
import Index from "./component/Admin/Index";
|
||||
import SiteInformation from "./component/Admin/Setting/SiteInformation";
|
||||
import Access from "./component/Admin/Setting/Access";
|
||||
import Mail from "./component/Admin/Setting/Mail";
|
||||
import UploadDownload from "./component/Admin/Setting/UploadDownload";
|
||||
import VAS from "./component/Admin/Setting/VAS";
|
||||
import Theme from "./component/Admin/Setting/Theme";
|
||||
import ImageSetting from "./component/Admin/Setting/Image";
|
||||
import Policy from "./component/Admin/Policy/Policy";
|
||||
|
|
@ -27,9 +27,11 @@ import UserForm from "./component/Admin/User/UserForm";
|
|||
import EditUserPreload from "./component/Admin/User/EditUser";
|
||||
import File from "./component/Admin/File/File";
|
||||
import Share from "./component/Admin/Share/Share";
|
||||
import Order from "./component/Admin/Order/Order";
|
||||
import Download from "./component/Admin/Task/Download";
|
||||
import Task from "./component/Admin/Task/Task";
|
||||
import Import from "./component/Admin/File/Import";
|
||||
import ReportList from "./component/Admin/Report/ReportList";
|
||||
import Captcha from "./component/Admin/Setting/Captcha";
|
||||
import Node from "./component/Admin/Node/Node";
|
||||
import AddNode from "./component/Admin/Node/AddNode";
|
||||
|
|
@ -120,6 +122,10 @@ export default function Admin() {
|
|||
<UploadDownload />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}/vas`}>
|
||||
<VAS />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}/theme`}>
|
||||
<Theme />
|
||||
</Route>
|
||||
|
|
@ -189,6 +195,10 @@ export default function Admin() {
|
|||
<Share />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}/order`} exact>
|
||||
<Order />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}/download`} exact>
|
||||
<Download />
|
||||
</Route>
|
||||
|
|
@ -197,6 +207,10 @@ export default function Admin() {
|
|||
<Task />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}/report`} exact>
|
||||
<ReportList />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}/node`} exact>
|
||||
<Node />
|
||||
</Route>
|
||||
|
|
|
|||
41
src/App.js
41
src/App.js
|
|
@ -9,6 +9,7 @@ import { useSelector } from "react-redux";
|
|||
import { Redirect, Route, Switch, useRouteMatch } from "react-router-dom";
|
||||
import Auth from "./middleware/Auth";
|
||||
import { CssBaseline, makeStyles, ThemeProvider } from "@material-ui/core";
|
||||
import PageLoading from "./component/Placeholder/PageLoading.js";
|
||||
import { changeThemeColor } from "./utils";
|
||||
import NotFound from "./component/Share/NotFound";
|
||||
// Lazy loads
|
||||
|
|
@ -21,16 +22,19 @@ import Download from "./component/Download/Download";
|
|||
import SharePreload from "./component/Share/SharePreload";
|
||||
import DocViewer from "./component/Viewer/Doc";
|
||||
import TextViewer from "./component/Viewer/Text";
|
||||
import Quota from "./component/VAS/Quota";
|
||||
import BuyQuota from "./component/VAS/BuyQuota";
|
||||
import WebDAV from "./component/Setting/WebDAV";
|
||||
import Tasks from "./component/Setting/Tasks";
|
||||
import Profile from "./component/Setting/Profile";
|
||||
import UserSetting from "./component/Setting/UserSetting";
|
||||
import QQCallback from "./component/Login/QQ";
|
||||
import Register from "./component/Login/Register";
|
||||
import Activation from "./component/Login/Activication";
|
||||
import ResetForm from "./component/Login/ResetForm";
|
||||
import Reset from "./component/Login/Reset";
|
||||
import PageLoading from "./component/Placeholder/PageLoading";
|
||||
import CodeViewer from "./component/Viewer/Code";
|
||||
import SiteNotice from "./component/Modals/SiteNotice";
|
||||
import MusicPlayer from "./component/FileManager/MusicPlayer";
|
||||
import EpubViewer from "./component/Viewer/Epub";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
|
@ -122,7 +126,10 @@ export default function App() {
|
|||
</AuthRoute>
|
||||
|
||||
<AuthRoute path={`${path}home`} isLogin={isLogin}>
|
||||
<FileManager />
|
||||
<>
|
||||
<SiteNotice />
|
||||
<FileManager />
|
||||
</>
|
||||
</AuthRoute>
|
||||
|
||||
<AuthRoute path={`${path}video`} isLogin={isLogin}>
|
||||
|
|
@ -163,18 +170,32 @@ export default function App() {
|
|||
<SearchResult />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}setting`} isLogin={isLogin}>
|
||||
<UserSetting />
|
||||
</Route>
|
||||
<AuthRoute path={`${path}quota`} isLogin={isLogin}>
|
||||
<Quota />
|
||||
</AuthRoute>
|
||||
|
||||
<Route
|
||||
<AuthRoute path={`${path}buy`} isLogin={isLogin}>
|
||||
<BuyQuota />
|
||||
</AuthRoute>
|
||||
|
||||
<AuthRoute
|
||||
path={`${path}setting`}
|
||||
isLogin={isLogin}
|
||||
>
|
||||
<UserSetting />
|
||||
</AuthRoute>
|
||||
|
||||
<AuthRoute
|
||||
path={`${path}profile/:id`}
|
||||
isLogin={isLogin}
|
||||
>
|
||||
<Profile />
|
||||
</Route>
|
||||
</AuthRoute>
|
||||
|
||||
<AuthRoute path={`${path}webdav`} isLogin={isLogin}>
|
||||
<AuthRoute
|
||||
path={`${path}connect`}
|
||||
isLogin={isLogin}
|
||||
>
|
||||
<WebDAV />
|
||||
</AuthRoute>
|
||||
|
||||
|
|
@ -210,6 +231,10 @@ export default function App() {
|
|||
<Reset />
|
||||
</Route>
|
||||
|
||||
<Route path={`${path}login/qq`}>
|
||||
<QQCallback />
|
||||
</Route>
|
||||
|
||||
<Route exact path={`${path}s/:id`}>
|
||||
<SharePreload />
|
||||
</Route>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
|
||||
it("renders without crashing", () => {
|
||||
const div = document.createElement("div");
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import React, { useCallback } from "react";
|
||||
import Autocomplete from "@material-ui/lab/Autocomplete";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
|
||||
export default function FileSelector({ onChange, value, label }) {
|
||||
const [selectValue, setSelectValue] = React.useState(
|
||||
value.map((v) => {
|
||||
return {
|
||||
ID: v,
|
||||
Name: "文件ID " + v,
|
||||
};
|
||||
})
|
||||
);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [inputValue, setInputValue] = React.useState("");
|
||||
const [options, setOptions] = React.useState([]);
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
let active = true;
|
||||
if (
|
||||
inputValue === "" ||
|
||||
selectValue.findIndex((v) => v.ID.toString() === inputValue) >= 0
|
||||
) {
|
||||
setOptions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
API.post("/admin/file/list", {
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
order_by: "id desc",
|
||||
conditions: {
|
||||
id: inputValue,
|
||||
},
|
||||
searches: {},
|
||||
})
|
||||
.then((response) => {
|
||||
if (active) {
|
||||
let newOptions = [];
|
||||
newOptions = [...newOptions, ...response.data.items];
|
||||
setOptions(newOptions);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [selectValue, inputValue]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
multiple
|
||||
style={{ width: 300 }}
|
||||
options={options}
|
||||
getOptionLabel={(option) =>
|
||||
typeof option === "string" ? option : option.Name
|
||||
}
|
||||
filterOptions={(x) => x}
|
||||
loading={loading}
|
||||
autoComplete
|
||||
includeInputInList
|
||||
filterSelectedOptions
|
||||
value={selectValue}
|
||||
onInputChange={(event, newInputValue) => {
|
||||
setInputValue(newInputValue);
|
||||
}}
|
||||
onChange={(event, newValue) => {
|
||||
setSelectValue(newValue);
|
||||
onChange(JSON.stringify(newValue.map((v) => v.ID)));
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label={label} type={"number"} />
|
||||
)}
|
||||
renderOption={(option) => (
|
||||
<Typography noWrap>{option.Name}</Typography>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
import { withStyles } from "@material-ui/core";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { lighten, makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
import MuiExpansionPanel from "@material-ui/core/ExpansionPanel";
|
||||
import MuiExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
|
||||
import MuiExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import List from "@material-ui/core/List";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import MenuIcon from "@material-ui/icons/Menu";
|
||||
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
|
||||
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import { lighten, makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import UserAvatar from "../Navbar/UserAvatar";
|
||||
import {
|
||||
Assignment,
|
||||
AttachMoney,
|
||||
Category,
|
||||
CloudDownload,
|
||||
Contactless,
|
||||
Contacts,
|
||||
Group,
|
||||
Home,
|
||||
|
|
@ -27,23 +31,22 @@ import {
|
|||
Mail,
|
||||
Palette,
|
||||
Person,
|
||||
Report,
|
||||
Settings,
|
||||
SettingsEthernet,
|
||||
Share,
|
||||
ShoppingCart,
|
||||
Storage,
|
||||
Contactless,
|
||||
} from "@material-ui/icons";
|
||||
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
|
||||
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
|
||||
import MenuIcon from "@material-ui/icons/Menu";
|
||||
import clsx from "clsx";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { withStyles } from "@material-ui/core";
|
||||
import MuiExpansionPanel from "@material-ui/core/ExpansionPanel";
|
||||
import MuiExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
|
||||
import MuiExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import { useRouteMatch } from "react-router-dom";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { changeSubTitle } from "../../redux/viewUpdate/action";
|
||||
import pathHelper from "../../utils/page";
|
||||
import UserAvatar from "../Navbar/UserAvatar";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ExpansionPanel = withStyles({
|
||||
|
|
@ -207,6 +210,11 @@ const items = [
|
|||
path: "upload",
|
||||
icon: <SettingsEthernet />,
|
||||
},
|
||||
{
|
||||
title: "vas.vas",
|
||||
path: "vas",
|
||||
icon: <AttachMoney />,
|
||||
},
|
||||
{
|
||||
title: "nav.appearance",
|
||||
path: "theme",
|
||||
|
|
@ -254,6 +262,16 @@ const items = [
|
|||
icon: <Share />,
|
||||
path: "share",
|
||||
},
|
||||
{
|
||||
title: "vas.reports",
|
||||
icon: <Report />,
|
||||
path: "report",
|
||||
},
|
||||
{
|
||||
title: "vas.orders",
|
||||
icon: <ShoppingCart />,
|
||||
path: "order",
|
||||
},
|
||||
{
|
||||
title: "nav.tasks",
|
||||
icon: <Assignment />,
|
||||
|
|
@ -393,6 +411,7 @@ export default function Dashboard({ content }) {
|
|||
);
|
||||
}
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<ExpansionPanel
|
||||
key={item.title}
|
||||
square
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import API from "../../../middleware/Api";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
formContainer: {
|
||||
|
|
@ -22,18 +23,40 @@ const useStyles = makeStyles(() => ({
|
|||
},
|
||||
}));
|
||||
|
||||
export default function AddGroup({ open, onClose, onSubmit }) {
|
||||
const defaultGroup = {
|
||||
name: "",
|
||||
group_id: 2,
|
||||
time: "",
|
||||
price: "",
|
||||
score: "",
|
||||
des: "",
|
||||
highlight: false,
|
||||
};
|
||||
|
||||
const groupEditToForm = (target) => {
|
||||
return {
|
||||
...target,
|
||||
time: (target.time / 86400).toString(),
|
||||
price: (target.price / 100).toString(),
|
||||
score: target.score.toString(),
|
||||
des: target.des.join("\n"),
|
||||
};
|
||||
};
|
||||
|
||||
export default function AddGroup({ open, onClose, onSubmit, groupEdit }) {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const { t: tCommon } = useTranslation("common");
|
||||
const classes = useStyles();
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [group, setGroup] = useState({
|
||||
name: "",
|
||||
group_id: 2,
|
||||
time: "",
|
||||
price: "",
|
||||
score: "",
|
||||
des: "",
|
||||
highlight: false,
|
||||
});
|
||||
const [group, setGroup] = useState(defaultGroup);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupEdit) {
|
||||
setGroup(groupEditToForm(groupEdit));
|
||||
} else {
|
||||
setGroup(defaultGroup);
|
||||
}
|
||||
}, [groupEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && groups.length === 0) {
|
||||
|
|
@ -67,9 +90,9 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
groupCopy.time = parseInt(groupCopy.time) * 86400;
|
||||
groupCopy.price = parseInt(groupCopy.price) * 100;
|
||||
groupCopy.score = parseInt(groupCopy.score);
|
||||
groupCopy.id = new Date().valueOf();
|
||||
groupCopy.id = groupEdit ? groupEdit.id : new Date().valueOf();
|
||||
groupCopy.des = groupCopy.des.split("\n");
|
||||
onSubmit(groupCopy);
|
||||
onSubmit(groupCopy, groupEdit !== null);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -83,14 +106,14 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
>
|
||||
<form onSubmit={submit}>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
添加可购用户组
|
||||
{groupEdit ? t("editMembership") : t("addMembership")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
名称
|
||||
{t("name")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={group.name}
|
||||
|
|
@ -98,7 +121,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
商品展示名称
|
||||
{t("productNameDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -106,7 +129,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
用户组
|
||||
{t("group")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={group.group_id}
|
||||
|
|
@ -125,7 +148,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
})}
|
||||
</Select>
|
||||
<FormHelperText id="component-helper-text">
|
||||
购买后升级的用户组
|
||||
{t("groupDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -133,7 +156,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
有效期 (天)
|
||||
{t("durationDay")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -146,7 +169,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
单位购买时间的有效期
|
||||
{t("durationGroupDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -154,7 +177,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
单价 (元)
|
||||
{t("priceYuan")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -167,7 +190,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
用户组的单价
|
||||
{t("groupPriceDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -175,7 +198,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
单价 (积分)
|
||||
{t("priceCredits")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -188,8 +211,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
使用积分购买时的价格,填写为 0
|
||||
表示不能使用积分购买
|
||||
{t("priceCreditsDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -197,7 +219,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
商品描述 (一行一个)
|
||||
{t("productDescription")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={group.des}
|
||||
|
|
@ -207,7 +229,7 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
购买页面展示的商品描述
|
||||
{t("productDescriptionDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -223,10 +245,10 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
)}
|
||||
/>
|
||||
}
|
||||
label="突出展示"
|
||||
label={t("highlight")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
开启后,在商品选择页面会被突出展示
|
||||
{t("highlightDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -234,10 +256,10 @@ export default function AddGroup({ open, onClose, onSubmit }) {
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="default">
|
||||
取消
|
||||
{tCommon("cancel")}
|
||||
</Button>
|
||||
<Button type={"submit"} color="primary">
|
||||
确定
|
||||
{tCommon("ok")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
|
|
@ -11,6 +11,7 @@ import FormHelperText from "@material-ui/core/FormHelperText";
|
|||
import FormControl from "@material-ui/core/FormControl";
|
||||
import SizeInput from "../Common/SizeInput";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
formContainer: {
|
||||
|
|
@ -18,15 +19,37 @@ const useStyles = makeStyles(() => ({
|
|||
},
|
||||
}));
|
||||
|
||||
export default function AddPack({ open, onClose, onSubmit }) {
|
||||
const packEditToForm = (target) => {
|
||||
return {
|
||||
...target,
|
||||
size: target.size.toString(),
|
||||
time: (target.time / 86400).toString(),
|
||||
price: (target.price / 100).toString(),
|
||||
score: target.score.toString(),
|
||||
};
|
||||
};
|
||||
|
||||
const defaultPack = {
|
||||
name: "",
|
||||
size: "1073741824",
|
||||
time: "",
|
||||
price: "",
|
||||
score: "",
|
||||
};
|
||||
|
||||
export default function AddPack({ open, onClose, onSubmit, packEdit }) {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const { t: tCommon } = useTranslation("common");
|
||||
const classes = useStyles();
|
||||
const [pack, setPack] = useState({
|
||||
name: "",
|
||||
size: "1073741824",
|
||||
time: "",
|
||||
price: "",
|
||||
score: "",
|
||||
});
|
||||
const [pack, setPack] = useState(defaultPack);
|
||||
|
||||
useEffect(() => {
|
||||
if (packEdit) {
|
||||
setPack(packEditToForm(packEdit));
|
||||
} else {
|
||||
setPack(defaultPack);
|
||||
}
|
||||
}, [packEdit]);
|
||||
|
||||
const handleChange = (name) => (event) => {
|
||||
setPack({
|
||||
|
|
@ -42,8 +65,8 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
packCopy.time = parseInt(packCopy.time) * 86400;
|
||||
packCopy.price = parseInt(packCopy.price) * 100;
|
||||
packCopy.score = parseInt(packCopy.score);
|
||||
packCopy.id = new Date().valueOf();
|
||||
onSubmit(packCopy);
|
||||
packCopy.id = packEdit ? packEdit.id : new Date().valueOf();
|
||||
onSubmit(packCopy, packEdit !== null);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -55,13 +78,15 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
maxWidth={"xs"}
|
||||
>
|
||||
<form onSubmit={submit}>
|
||||
<DialogTitle id="alert-dialog-title">添加容量包</DialogTitle>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{packEdit ? t("editStoragePack") : t("addStoragePack")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
名称
|
||||
{t("name")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={pack.name}
|
||||
|
|
@ -69,7 +94,7 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
商品展示名称
|
||||
{t("productNameDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -80,12 +105,12 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
value={pack.size}
|
||||
onChange={handleChange("size")}
|
||||
min={1}
|
||||
label={"大小"}
|
||||
label={t("size")}
|
||||
max={9223372036854775807}
|
||||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
容量包的大小
|
||||
{t("packSizeDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -93,7 +118,7 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
有效期 (天)
|
||||
{t("durationDay")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -106,7 +131,7 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
每个容量包的有效期
|
||||
{t("durationDayDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -114,7 +139,7 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
单价 (元)
|
||||
{t("priceYuan")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -127,7 +152,7 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
容量包的单价
|
||||
{t("packPriceDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -135,7 +160,7 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
单价 (积分)
|
||||
{t("priceCredits")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -148,8 +173,7 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
使用积分购买时的价格,填写为 0
|
||||
表示不能使用积分购买
|
||||
{t("priceCreditsDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -157,10 +181,10 @@ export default function AddPack({ open, onClose, onSubmit }) {
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="default">
|
||||
取消
|
||||
{tCommon("cancel")}
|
||||
</Button>
|
||||
<Button type={"submit"} color="primary">
|
||||
确定
|
||||
{tCommon("ok")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import React from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardActionArea from "@material-ui/core/CardActionArea";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import CardMedia from "@material-ui/core/CardMedia";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import CardMedia from "@material-ui/core/CardMedia";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import { useHistory } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
|
@ -99,8 +99,8 @@ export default function AddPolicy({ open, onClose }) {
|
|||
</DialogTitle>
|
||||
<DialogContent dividers className={classes.bg}>
|
||||
<Grid container spacing={2}>
|
||||
{policies.map((v) => (
|
||||
<Grid item sm={12} md={6} key={v.path}>
|
||||
{policies.map((v, index) => (
|
||||
<Grid key={index} item sm={12} md={6}>
|
||||
<Card className={classes.card}>
|
||||
<CardActionArea
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
formContainer: {
|
||||
|
|
@ -23,6 +24,9 @@ const useStyles = makeStyles(() => ({
|
|||
}));
|
||||
|
||||
export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const { t: tCommon } = useTranslation("common");
|
||||
const { t: tApp } = useTranslation();
|
||||
const classes = useStyles();
|
||||
const [input, setInput] = useState({
|
||||
num: 1,
|
||||
|
|
@ -85,13 +89,15 @@ export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
|||
maxWidth={"xs"}
|
||||
>
|
||||
<form onSubmit={submit}>
|
||||
<DialogTitle id="alert-dialog-title">生成兑换码</DialogTitle>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{t("generateGiftCode")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
生成数量
|
||||
{t("numberOfCodes")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -105,7 +111,7 @@ export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
激活码批量生成数量
|
||||
{t("numberOfCodesDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -113,7 +119,7 @@ export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
对应商品
|
||||
{t("linkedProduct")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={input.id}
|
||||
|
|
@ -130,7 +136,9 @@ export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
|||
{v.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem value={0}>积分</MenuItem>
|
||||
<MenuItem value={0}>
|
||||
{tApp("vas.credits")}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -138,7 +146,7 @@ export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
|||
<div className={classes.formContainer}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
商品数量
|
||||
{t("productQyt")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
|
|
@ -151,7 +159,7 @@ export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
|||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
对于积分类商品,此处为积分数量,其他商品为时长倍数
|
||||
{t("productQytDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -163,10 +171,10 @@ export default function AddRedeem({ open, onClose, products, onSuccess }) {
|
|||
onClick={onClose}
|
||||
color="default"
|
||||
>
|
||||
取消
|
||||
{tCommon("cancel")}
|
||||
</Button>
|
||||
<Button disabled={loading} type={"submit"} color="primary">
|
||||
确定
|
||||
{tCommon("ok")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import AppBar from "@material-ui/core/AppBar";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import { CompactPicker } from "react-color";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import Fab from "@material-ui/core/Fab";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { createMuiTheme, makeStyles } from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import { createMuiTheme, makeStyles } from "@material-ui/core/styles";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Add, Menu } from "@material-ui/icons";
|
||||
import { ThemeProvider } from "@material-ui/styles";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { CompactPicker } from "react-color";
|
||||
import Fab from "@material-ui/core/Fab";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function FileFilter({ setFilter, setSearch, open, onClose }) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import React from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const magicVars = [
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import React, { useState } from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ShareFilter({ setFilter, setSearch, open, onClose }) {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function UserFilter({ setFilter, setSearch, open, onClose }) {
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
import { lighten } from "@material-ui/core";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Delete, DeleteForever, FilterList,LinkOff } from "@material-ui/icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import { sizeToString } from "../../../utils";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import { useHistory } from "react-router";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Delete, DeleteForever, FilterList, LinkOff } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { lighten } from "@material-ui/core";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import FileFilter from "../Dialogs/FileFilter";
|
||||
import { formatLocalTime } from "../../../utils/datetime";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
|
@ -95,6 +95,7 @@ export default function File() {
|
|||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import { Dialog } from "@material-ui/core";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Fade from "@material-ui/core/Fade";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import Fade from "@material-ui/core/Fade";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Popper from "@material-ui/core/Popper";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import { Dialog } from "@material-ui/core";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import PathSelector from "../../FileManager/PathSelector";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import API from "../../../middleware/Api";
|
||||
import PathSelector from "../../FileManager/PathSelector";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -365,7 +365,7 @@ export default function Import() {
|
|||
>
|
||||
{users.map((u) => (
|
||||
<MenuItem
|
||||
key={u.Email}
|
||||
key={u.ID}
|
||||
onClick={() =>
|
||||
selectUser(u)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,15 @@ export default function EditGroupPreload() {
|
|||
[
|
||||
"archive_download",
|
||||
"archive_task",
|
||||
"relocate",
|
||||
"one_time_download",
|
||||
"share_download",
|
||||
"webdav_proxy",
|
||||
"share_free",
|
||||
"aria2",
|
||||
"redirected_source",
|
||||
"advance_delete"
|
||||
"advance_delete",
|
||||
"select_node",
|
||||
].forEach((v) => {
|
||||
if (response.data.OptionsSerialized[v] !== undefined) {
|
||||
response.data.OptionsSerialized[v] = response.data
|
||||
|
|
@ -56,12 +59,22 @@ export default function EditGroupPreload() {
|
|||
"aria2_batch",
|
||||
].forEach((v) => {
|
||||
if (response.data.OptionsSerialized[v] !== undefined) {
|
||||
response.data.OptionsSerialized[
|
||||
v
|
||||
] = response.data.OptionsSerialized[v].toString();
|
||||
response.data.OptionsSerialized[v] =
|
||||
response.data.OptionsSerialized[v].toString();
|
||||
}
|
||||
});
|
||||
response.data.PolicyList = response.data.PolicyList[0];
|
||||
response.data.PolicyList = response.data.PolicyList.map((v) => {
|
||||
return v.toString();
|
||||
});
|
||||
|
||||
response.data.OptionsSerialized.available_nodes = response.data
|
||||
.OptionsSerialized.available_nodes
|
||||
? response.data.OptionsSerialized.available_nodes.map(
|
||||
(v) => {
|
||||
return v.toString();
|
||||
}
|
||||
)
|
||||
: [];
|
||||
|
||||
// JSON转换
|
||||
if (
|
||||
|
|
@ -70,9 +83,10 @@ export default function EditGroupPreload() {
|
|||
response.data.OptionsSerialized.aria2_options = "{}";
|
||||
} else {
|
||||
try {
|
||||
response.data.OptionsSerialized.aria2_options = JSON.stringify(
|
||||
response.data.OptionsSerialized.aria2_options
|
||||
);
|
||||
response.data.OptionsSerialized.aria2_options =
|
||||
JSON.stringify(
|
||||
response.data.OptionsSerialized.aria2_options
|
||||
);
|
||||
} catch (e) {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import { Delete, Edit } from "@material-ui/icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import { sizeToString } from "../../../utils";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Delete, Edit } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import SizeInput from "../Common/SizeInput";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import API from "../../../middleware/Api";
|
||||
import SizeInput from "../Common/SizeInput";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Link } from "@material-ui/core";
|
||||
import { getSelectItemStyles } from "../../../utils";
|
||||
import NodeSelector from "./NodeSelector";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
|
|
@ -38,17 +41,9 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// function getStyles(name, personName, theme) {
|
||||
// return {
|
||||
// fontWeight:
|
||||
// personName.indexOf(name) === -1
|
||||
// ? theme.typography.fontWeightRegular
|
||||
// : theme.typography.fontWeightMedium
|
||||
// };
|
||||
// }
|
||||
|
||||
export default function GroupForm(props) {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "group" });
|
||||
const { t: tVas } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const { t: tDashboard } = useTranslation("dashboard");
|
||||
const classes = useStyles();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -62,7 +57,7 @@ export default function GroupForm(props) {
|
|||
ShareEnabled: "true", // 转换类型
|
||||
WebDAVEnabled: "true", // 转换类型
|
||||
SpeedLimit: "0", // 转换类型
|
||||
PolicyList: 1, // 转换类型,至少选择一个
|
||||
PolicyList: ["1"], // 转换类型,至少选择一个
|
||||
OptionsSerialized: {
|
||||
// 批量转换类型
|
||||
share_download: "true",
|
||||
|
|
@ -71,11 +66,13 @@ export default function GroupForm(props) {
|
|||
decompress_size: "0",
|
||||
source_batch: "0",
|
||||
aria2_batch: "1",
|
||||
available_nodes: [],
|
||||
},
|
||||
}
|
||||
);
|
||||
const [policies, setPolicies] = useState({});
|
||||
|
||||
const theme = useTheme();
|
||||
const history = useHistory();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -154,12 +151,15 @@ export default function GroupForm(props) {
|
|||
[
|
||||
"archive_download",
|
||||
"archive_task",
|
||||
"relocate",
|
||||
"one_time_download",
|
||||
"share_download",
|
||||
"webdav_proxy",
|
||||
"share_free",
|
||||
"aria2",
|
||||
"redirected_source",
|
||||
"advance_delete"
|
||||
"advance_delete",
|
||||
"select_node",
|
||||
].forEach((v) => {
|
||||
if (groupCopy.OptionsSerialized[v] !== undefined) {
|
||||
groupCopy.OptionsSerialized[v] =
|
||||
|
|
@ -183,7 +183,21 @@ export default function GroupForm(props) {
|
|||
);
|
||||
}
|
||||
});
|
||||
groupCopy.PolicyList = [parseInt(groupCopy.PolicyList)];
|
||||
|
||||
groupCopy.PolicyList = groupCopy.PolicyList.map((v) => {
|
||||
return parseInt(v);
|
||||
});
|
||||
|
||||
groupCopy.OptionsSerialized.available_nodes =
|
||||
groupCopy.OptionsSerialized.available_nodes.map((v) => {
|
||||
return parseInt(v);
|
||||
});
|
||||
|
||||
if (groupCopy.PolicyList.length < 1 && groupCopy.ID !== 3) {
|
||||
ToggleSnackbar("top", "right", t("atLeastOnePolicy"), "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
// JSON转换
|
||||
try {
|
||||
groupCopy.OptionsSerialized.aria2_options = JSON.parse(
|
||||
|
|
@ -221,7 +235,8 @@ export default function GroupForm(props) {
|
|||
<div className={classes.root}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{group.ID === 0 && t("new")}
|
||||
{group.ID !== 0 && t("editGroup", { group: group.Name })}
|
||||
{group.ID !== 0 &&
|
||||
t("editGroup", { group: group.Name })}
|
||||
</Typography>
|
||||
|
||||
<div className={classes.formContainer}>
|
||||
|
|
@ -246,11 +261,12 @@ export default function GroupForm(props) {
|
|||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{t("storagePolicy")}
|
||||
{t("availablePolicies")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="demo-mutiple-chip-label"
|
||||
id="demo-mutiple-chip"
|
||||
multiple
|
||||
value={group.PolicyList}
|
||||
onChange={handleChange(
|
||||
"PolicyList"
|
||||
|
|
@ -258,12 +274,36 @@ export default function GroupForm(props) {
|
|||
input={
|
||||
<Input id="select-multiple-chip" />
|
||||
}
|
||||
renderValue={(selected) => (
|
||||
<div>
|
||||
{selected.map((value) => (
|
||||
<Chip
|
||||
style={{
|
||||
margin: 2,
|
||||
}}
|
||||
key={value}
|
||||
size={"small"}
|
||||
label={
|
||||
policies[value]
|
||||
}
|
||||
className={
|
||||
classes.chip
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{Object.keys(policies).map(
|
||||
(pid) => (
|
||||
<MenuItem
|
||||
key={pid}
|
||||
value={pid}
|
||||
style={getSelectItemStyles(
|
||||
pid,
|
||||
group.PolicyList,
|
||||
theme
|
||||
)}
|
||||
>
|
||||
{policies[pid]}
|
||||
</MenuItem>
|
||||
|
|
@ -271,7 +311,7 @@ export default function GroupForm(props) {
|
|||
)}
|
||||
</Select>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("storageDes")}
|
||||
{t("availablePoliciesDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
|
@ -386,6 +426,28 @@ export default function GroupForm(props) {
|
|||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={
|
||||
group.OptionsSerialized
|
||||
.share_free === "true"
|
||||
}
|
||||
onChange={handleOptionCheckChange(
|
||||
"share_free"
|
||||
)}
|
||||
/>
|
||||
}
|
||||
label={tVas("freeDownload")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("freeDownloadDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{group.ID !== 3 && (
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
|
|
@ -511,7 +573,7 @@ export default function GroupForm(props) {
|
|||
multiline
|
||||
type={"number"}
|
||||
inputProps={{
|
||||
min: 1,
|
||||
min: 0,
|
||||
step: 1,
|
||||
}}
|
||||
value={
|
||||
|
|
@ -526,6 +588,46 @@ export default function GroupForm(props) {
|
|||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{t("availableNodes")}
|
||||
</InputLabel>
|
||||
<NodeSelector
|
||||
selected={
|
||||
group.OptionsSerialized
|
||||
.available_nodes
|
||||
}
|
||||
handleChange={handleOptionChange(
|
||||
"available_nodes"
|
||||
)}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("availableNodesDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={
|
||||
group.OptionsSerialized
|
||||
.select_node === "true"
|
||||
}
|
||||
onChange={handleOptionCheckChange(
|
||||
"select_node"
|
||||
)}
|
||||
/>
|
||||
}
|
||||
label={t("allowSelectNode")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("allowSelectNodeDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
</Collapse>
|
||||
|
||||
<div className={classes.form}>
|
||||
|
|
@ -626,7 +728,32 @@ export default function GroupForm(props) {
|
|||
<Switch
|
||||
checked={
|
||||
group.OptionsSerialized
|
||||
.redirected_source === "true"
|
||||
.relocate === "true"
|
||||
}
|
||||
onChange={handleOptionCheckChange(
|
||||
"relocate"
|
||||
)}
|
||||
/>
|
||||
}
|
||||
label={t("migratePolicy")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("migratePolicyDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{group.ID !== 3 && (
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={
|
||||
group.OptionsSerialized
|
||||
.redirected_source ===
|
||||
"true"
|
||||
}
|
||||
onChange={handleOptionCheckChange(
|
||||
"redirected_source"
|
||||
|
|
@ -638,10 +765,14 @@ export default function GroupForm(props) {
|
|||
<FormHelperText id="component-helper-text">
|
||||
<Trans
|
||||
ns={"dashboard"}
|
||||
i18nKey={"group.redirectedSourceDes"}
|
||||
i18nKey={
|
||||
"group.redirectedSourceDes"
|
||||
}
|
||||
components={[
|
||||
<Link
|
||||
href={tDashboard("policy.comparesStoragePoliciesLink")}
|
||||
href={tDashboard(
|
||||
"policy.comparesStoragePoliciesLink"
|
||||
)}
|
||||
key={0}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import { getSelectItemStyles } from "../../../utils";
|
||||
import { useTheme } from "@material-ui/core/styles";
|
||||
|
||||
export default function NodeSelector({ selected, handleChange }) {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "group" });
|
||||
const [nodes, setNodes] = useState({});
|
||||
const theme = useTheme();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
API.post("/admin/node/list", {
|
||||
page: 1,
|
||||
page_size: 10000,
|
||||
order_by: "id asc",
|
||||
conditions: {},
|
||||
})
|
||||
.then((response) => {
|
||||
const res = {};
|
||||
response.data.items.forEach((v) => {
|
||||
res[v.ID] = v.Name;
|
||||
});
|
||||
setNodes(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Select
|
||||
labelId="demo-mutiple-chip-label"
|
||||
id="demo-mutiple-chip"
|
||||
multiple
|
||||
value={selected}
|
||||
onChange={handleChange}
|
||||
input={<Input id="select-multiple-chip" />}
|
||||
renderValue={(selected) => (
|
||||
<div>
|
||||
{selected.map((value) => (
|
||||
<Chip
|
||||
style={{
|
||||
margin: 2,
|
||||
}}
|
||||
key={value}
|
||||
size={"small"}
|
||||
label={nodes[value]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{Object.keys(nodes).map((pid) => (
|
||||
<MenuItem
|
||||
key={pid}
|
||||
value={pid}
|
||||
style={getSelectItemStyles(pid, selected, theme)}
|
||||
>
|
||||
{nodes[pid]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,22 +1,26 @@
|
|||
import Avatar from "@material-ui/core/Avatar";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import { blue, green, red, yellow } from "@material-ui/core/colors";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { ResponsiveContainer } from "recharts/lib/component/ResponsiveContainer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import pathHelper from "../../utils/page";
|
||||
import API from "../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Avatar from "@material-ui/core/Avatar";
|
||||
import {
|
||||
Description,
|
||||
Favorite,
|
||||
|
|
@ -30,23 +34,19 @@ import {
|
|||
Public,
|
||||
Telegram,
|
||||
} from "@material-ui/icons";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import { blue, green, red, yellow } from "@material-ui/core/colors";
|
||||
import axios from "axios";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { ResponsiveContainer } from "recharts/lib/component/ResponsiveContainer";
|
||||
import TimeAgo from "timeago-react";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import API from "../../middleware/Api";
|
||||
import pathHelper from "../../utils/page";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -99,8 +99,8 @@ export default function Index() {
|
|||
const { t } = useTranslation("dashboard");
|
||||
const classes = useStyles();
|
||||
const [lineData, setLineData] = useState([]);
|
||||
const [news, setNews] = useState([]);
|
||||
const [newsUsers, setNewsUsers] = useState({});
|
||||
// const [news, setNews] = useState([]);
|
||||
// const [newsUsers, setNewsUsers] = useState({});
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [siteURL, setSiteURL] = React.useState("");
|
||||
const [statistics, setStatistics] = useState({
|
||||
|
|
@ -171,26 +171,26 @@ export default function Index() {
|
|||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
|
||||
axios
|
||||
.get("/api/v3/admin/news?tag=" + t("summary.newsTag"))
|
||||
.then((response) => {
|
||||
setNews(response.data.data);
|
||||
const res = {};
|
||||
response.data.included.forEach((v) => {
|
||||
if (v.type === "users") {
|
||||
res[v.id] = v.attributes;
|
||||
}
|
||||
});
|
||||
setNewsUsers(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
t("summary.newsletterError"),
|
||||
"warning"
|
||||
);
|
||||
});
|
||||
// axios
|
||||
// .get("/api/v3/admin/news?tag=" + t("summary.newsTag"))
|
||||
// .then((response) => {
|
||||
// setNews(response.data.data);
|
||||
// const res = {};
|
||||
// response.data.included.forEach((v) => {
|
||||
// if (v.type === "users") {
|
||||
// res[v.id] = v.attributes;
|
||||
// }
|
||||
// });
|
||||
// setNewsUsers(res);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// ToggleSnackbar(
|
||||
// "top",
|
||||
// "right",
|
||||
// t("summary.newsletterError"),
|
||||
// "warning"
|
||||
// );
|
||||
// });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
@ -335,8 +335,8 @@ export default function Index() {
|
|||
</Typography>
|
||||
<Typography className={classes.version}>
|
||||
{version.backend}{" "}
|
||||
{version.is_pro === "true" && (
|
||||
<Chip size="small" label="Pro" />
|
||||
{version.is_plus === "true" && (
|
||||
<Chip size="small" label="Plus" />
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
|
|
@ -369,7 +369,7 @@ export default function Index() {
|
|||
<ListItemIcon>
|
||||
<GitHub />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="GitHub" />
|
||||
<ListItemText primary={t("summary.github")} />
|
||||
<ListItemIcon className={classes.iconRight}>
|
||||
<Launch />
|
||||
</ListItemIcon>
|
||||
|
|
@ -423,7 +423,7 @@ export default function Index() {
|
|||
<ListItem
|
||||
button
|
||||
onClick={() =>
|
||||
window.open("https://cloudreve.org/pro")
|
||||
window.open("https://docs.cloudreve.org/use/pro/jie-shao")
|
||||
}
|
||||
>
|
||||
<ListItemIcon style={{ color: "#ff789d" }}>
|
||||
|
|
@ -438,7 +438,7 @@ export default function Index() {
|
|||
</div>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={8} lg={9}>
|
||||
{/* <Grid item xs={12} md={8} lg={9}>
|
||||
<Paper className={classes.paper}>
|
||||
<List>
|
||||
{news &&
|
||||
|
|
@ -521,7 +521,7 @@ export default function Index() {
|
|||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,432 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { lighten } from "@material-ui/core";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import ShareFilter from "../Dialogs/ShareFilter";
|
||||
import { formatLocalTime } from "../../../utils/datetime";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
[theme.breakpoints.up("md")]: {
|
||||
marginLeft: 100,
|
||||
},
|
||||
marginBottom: 40,
|
||||
},
|
||||
content: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
container: {
|
||||
overflowX: "auto",
|
||||
},
|
||||
tableContainer: {
|
||||
marginTop: 16,
|
||||
},
|
||||
header: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
headerRight: {},
|
||||
highlight:
|
||||
theme.palette.type === "light"
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85),
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark,
|
||||
},
|
||||
visuallyHidden: {
|
||||
border: 0,
|
||||
clip: "rect(0 0 0 0)",
|
||||
height: 1,
|
||||
margin: -1,
|
||||
overflow: "hidden",
|
||||
padding: 0,
|
||||
position: "absolute",
|
||||
top: 20,
|
||||
width: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function Order() {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const { t: tDashboard } = useTranslation("dashboard");
|
||||
const classes = useStyles();
|
||||
const [orders, setOrders] = useState([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [filter, setFilter] = useState({});
|
||||
const [users, setUsers] = useState({});
|
||||
const [search, setSearch] = useState({});
|
||||
const [orderBy, setOrderBy] = useState(["id", "desc"]);
|
||||
const [filterDialog, setFilterDialog] = useState(false);
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const loadList = () => {
|
||||
API.post("/admin/order/list", {
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
order_by: orderBy.join(" "),
|
||||
conditions: filter,
|
||||
searches: search,
|
||||
})
|
||||
.then((response) => {
|
||||
setUsers(response.data.users);
|
||||
setOrders(response.data.items);
|
||||
setTotal(response.data.total);
|
||||
setSelected([]);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadList();
|
||||
}, [page, pageSize, orderBy, filter, search]);
|
||||
|
||||
const deletePolicy = (id) => {
|
||||
setLoading(true);
|
||||
API.post("/admin/order/delete", { id: [id] })
|
||||
.then(() => {
|
||||
loadList();
|
||||
ToggleSnackbar("top", "right", t("orderDeleted"), "success");
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const deleteBatch = () => {
|
||||
setLoading(true);
|
||||
API.post("/admin/order/delete", { id: selected })
|
||||
.then(() => {
|
||||
loadList();
|
||||
ToggleSnackbar("top", "right", t("orderDeleted"), "success");
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectAllClick = (event) => {
|
||||
if (event.target.checked) {
|
||||
const newSelecteds = orders.map((n) => n.ID);
|
||||
setSelected(newSelecteds);
|
||||
return;
|
||||
}
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleClick = (event, name) => {
|
||||
const selectedIndex = selected.indexOf(name);
|
||||
let newSelected = [];
|
||||
|
||||
if (selectedIndex === -1) {
|
||||
newSelected = newSelected.concat(selected, name);
|
||||
} else if (selectedIndex === 0) {
|
||||
newSelected = newSelected.concat(selected.slice(1));
|
||||
} else if (selectedIndex === selected.length - 1) {
|
||||
newSelected = newSelected.concat(selected.slice(0, -1));
|
||||
} else if (selectedIndex > 0) {
|
||||
newSelected = newSelected.concat(
|
||||
selected.slice(0, selectedIndex),
|
||||
selected.slice(selectedIndex + 1)
|
||||
);
|
||||
}
|
||||
|
||||
setSelected(newSelected);
|
||||
};
|
||||
|
||||
const isSelected = (id) => selected.indexOf(id) !== -1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ShareFilter
|
||||
filter={filter}
|
||||
open={filterDialog}
|
||||
onClose={() => setFilterDialog(false)}
|
||||
setSearch={setSearch}
|
||||
setFilter={setFilter}
|
||||
/>
|
||||
<div className={classes.header}>
|
||||
<div className={classes.headerRight}>
|
||||
<Button
|
||||
color={"primary"}
|
||||
onClick={() => loadList()}
|
||||
variant={"outlined"}
|
||||
>
|
||||
{tDashboard("policy.refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Paper square className={classes.tableContainer}>
|
||||
{selected.length > 0 && (
|
||||
<Toolbar className={classes.highlight}>
|
||||
<Typography
|
||||
style={{ flex: "1 1 100%" }}
|
||||
color="inherit"
|
||||
variant="subtitle1"
|
||||
>
|
||||
{tDashboard("user.selectedObjects", {
|
||||
num: selected.length,
|
||||
})}
|
||||
</Typography>
|
||||
<Tooltip title={tDashboard("policy.delete")}>
|
||||
<IconButton
|
||||
onClick={deleteBatch}
|
||||
disabled={loading}
|
||||
aria-label="delete"
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Toolbar>
|
||||
)}
|
||||
<TableContainer className={classes.container}>
|
||||
<Table aria-label="sticky table" size={"small"}>
|
||||
<TableHead>
|
||||
<TableRow style={{ height: 52 }}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
indeterminate={
|
||||
selected.length > 0 &&
|
||||
selected.length < orders.length
|
||||
}
|
||||
checked={
|
||||
orders.length > 0 &&
|
||||
selected.length === orders.length
|
||||
}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all desserts",
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 10 }}>
|
||||
<TableSortLabel
|
||||
active={orderBy[0] === "id"}
|
||||
direction={orderBy[1]}
|
||||
onClick={() =>
|
||||
setOrderBy([
|
||||
"id",
|
||||
orderBy[1] === "asc"
|
||||
? "desc"
|
||||
: "asc",
|
||||
])
|
||||
}
|
||||
>
|
||||
#
|
||||
{orderBy[0] === "id" ? (
|
||||
<span
|
||||
className={
|
||||
classes.visuallyHidden
|
||||
}
|
||||
>
|
||||
{orderBy[1] === "desc"
|
||||
? "sorted descending"
|
||||
: "sorted ascending"}
|
||||
</span>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 130 }}>
|
||||
{t("orderName")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 90 }}>
|
||||
{t("product")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 80 }}>
|
||||
<TableSortLabel
|
||||
active={orderBy[0] === "order_no"}
|
||||
direction={orderBy[1]}
|
||||
onClick={() =>
|
||||
setOrderBy([
|
||||
"order_no",
|
||||
orderBy[1] === "asc"
|
||||
? "desc"
|
||||
: "asc",
|
||||
])
|
||||
}
|
||||
>
|
||||
{t("orderNumber")}
|
||||
{orderBy[0] === "order_no" ? (
|
||||
<span
|
||||
className={
|
||||
classes.visuallyHidden
|
||||
}
|
||||
>
|
||||
{orderBy[1] === "desc"
|
||||
? "sorted descending"
|
||||
: "sorted ascending"}
|
||||
</span>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{ minWidth: 100 }}
|
||||
align={"right"}
|
||||
>
|
||||
{t("price")}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{ minWidth: 80 }}
|
||||
align={"right"}
|
||||
>
|
||||
{t("qyt")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 100 }}>
|
||||
{t("status")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 80 }}>
|
||||
{t("paidBy")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 100 }}>
|
||||
{t("orderOwner")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 150 }}>
|
||||
{tDashboard("file.createdAt")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 80 }}>
|
||||
{tDashboard("policy.actions")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{orders.map((row) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={row.ID}
|
||||
role="checkbox"
|
||||
selected={isSelected(row.ID)}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
onClick={(event) =>
|
||||
handleClick(event, row.ID)
|
||||
}
|
||||
checked={isSelected(row.ID)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{row.ID}</TableCell>
|
||||
<TableCell>{row.Name}</TableCell>
|
||||
<TableCell>
|
||||
{row.Type === 0 && t("storagePack")}
|
||||
{row.Type === 1 &&
|
||||
t("purchasableGroups")}
|
||||
{row.Type === 2 && t("credits")}
|
||||
</TableCell>
|
||||
<TableCell>{row.OrderNo}</TableCell>
|
||||
<TableCell align={"right"}>
|
||||
{row.Method === "score" && row.Price}
|
||||
{row.Method !== "score" && (
|
||||
<>
|
||||
¥{(row.Price / 100).toFixed(2)}
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align={"right"}>
|
||||
{row.Num}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{row.Status === 0 && t("unpaid")}
|
||||
{row.Status === 1 && t("paid")}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{row.Method === "score" && t("credits")}
|
||||
{row.Method === "alipay" && t("alipay")}
|
||||
{row.Method === "payjs" && t("payjs")}
|
||||
{row.Method === "custom" &&
|
||||
t("customPayment")}
|
||||
{row.Method === "weixin" &&
|
||||
t("wechatPay")}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Link
|
||||
href={
|
||||
"/admin/user/edit/" + row.UserID
|
||||
}
|
||||
>
|
||||
{users[row.UserID]
|
||||
? users[row.UserID].Nick
|
||||
: tDashboard(
|
||||
"file.unknownUploader"
|
||||
)}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{formatLocalTime(row.CreatedAt)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Tooltip
|
||||
title={tDashboard("policy.delete")}
|
||||
>
|
||||
<IconButton
|
||||
disabled={loading}
|
||||
onClick={() =>
|
||||
deletePolicy(row.ID)
|
||||
}
|
||||
size={"small"}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 25, 50, 100]}
|
||||
component="div"
|
||||
count={total}
|
||||
rowsPerPage={pageSize}
|
||||
page={page - 1}
|
||||
onChangePage={(e, p) => setPage(p + 1)}
|
||||
onChangeRowsPerPage={(e) => {
|
||||
setPageSize(e.target.value);
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { useParams } from "react-router";
|
||||
import COSGuide from "./Guid/COSGuide";
|
||||
import LocalGuide from "./Guid/LocalGuide";
|
||||
import OneDriveGuide from "./Guid/OneDriveGuide";
|
||||
import OSSGuide from "./Guid/OSSGuide";
|
||||
import QiniuGuide from "./Guid/QiniuGuide";
|
||||
import RemoteGuide from "./Guid/RemoteGuide";
|
||||
import QiniuGuide from "./Guid/QiniuGuide";
|
||||
import OSSGuide from "./Guid/OSSGuide";
|
||||
import UpyunGuide from "./Guid/UpyunGuide";
|
||||
import COSGuide from "./Guid/COSGuide";
|
||||
import OneDriveGuide from "./Guid/OneDriveGuide";
|
||||
import S3Guide from "./Guid/S3Guide";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { useParams } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import API from "../../../middleware/Api";
|
||||
import COSGuide from "./Guid/COSGuide";
|
||||
import EditPro from "./Guid/EditPro";
|
||||
import LocalGuide from "./Guid/LocalGuide";
|
||||
import OneDriveGuide from "./Guid/OneDriveGuide";
|
||||
import OSSGuide from "./Guid/OSSGuide";
|
||||
import QiniuGuide from "./Guid/QiniuGuide";
|
||||
import RemoteGuide from "./Guid/RemoteGuide";
|
||||
import QiniuGuide from "./Guid/QiniuGuide";
|
||||
import OSSGuide from "./Guid/OSSGuide";
|
||||
import UpyunGuide from "./Guid/UpyunGuide";
|
||||
import S3Guide from "./Guid/S3Guide";
|
||||
import COSGuide from "./Guid/COSGuide";
|
||||
import OneDriveGuide from "./Guid/OneDriveGuide";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import EditPro from "./Guid/EditPro";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { transformResponse } from "./utils";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import { useHistory } from "react-router";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import API from "../../../../middleware/Api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import { useHistory } from "react-router";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import AlertDialog from "../../Dialogs/Alert";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import { useHistory } from "react-router";
|
||||
import AlertDialog from "../../Dialogs/Alert";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
|
|
|||
|
|
@ -1,30 +1,29 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import { useHistory } from "react-router";
|
||||
import { getNumber } from "../../../../utils";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
stepContent: {
|
||||
padding: "16px 32px 16px 32px",
|
||||
|
|
@ -101,7 +100,6 @@ export default function RemoteGuide(props) {
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [skipped] = React.useState(new Set());
|
||||
const [magicVar, setMagicVar] = useState("");
|
||||
// const [useCDN, setUseCDN] = useState("false");
|
||||
const [policy, setPolicy] = useState(
|
||||
props.policy
|
||||
? props.policy
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import { getNumber, randomStr } from "../../../../utils";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import { useHistory } from "react-router";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import { getNumber, randomStr } from "../../../../utils";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
||||
|
|
@ -462,13 +462,13 @@ export default function RemoteGuide(props) {
|
|||
<Typography variant={"body2"}>
|
||||
<Trans
|
||||
ns={"dashboard"}
|
||||
i18nKey={"policy.filePathMagicVarDes"}
|
||||
i18nKey={"policy.pathMagicVarDesRemote"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
color={"secondary"}
|
||||
href={"javascript:void()"}
|
||||
onClick={() => setMagicVar("file")}
|
||||
onClick={() => setMagicVar("path")}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
|
@ -494,19 +494,21 @@ export default function RemoteGuide(props) {
|
|||
</div>
|
||||
<div className={classes.subStepContent}>
|
||||
<Typography variant={"body2"}>
|
||||
是否需要对存储的物理文件进行重命名?此处的重命名不会影响最终呈现给用户的
|
||||
文件名。文件名也可使用魔法变量,
|
||||
可用魔法变量可参考{" "}
|
||||
<Link
|
||||
color={"secondary"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setMagicVar("file");
|
||||
}}
|
||||
>
|
||||
文件名魔法变量列表
|
||||
</Link>{" "}
|
||||
。
|
||||
<Trans
|
||||
ns={"dashboard"}
|
||||
i18nKey={"policy.filePathMagicVarDes"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
color={"secondary"}
|
||||
href={"javascript:void()"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setMagicVar("file");
|
||||
}}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</Typography>
|
||||
<div className={classes.form}>
|
||||
<FormControl required component="fieldset">
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import { lighten, makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../../middleware/Api";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import DomainInput from "../../Common/DomainInput";
|
||||
import SizeInput from "../../Common/SizeInput";
|
||||
import MagicVar from "../../Dialogs/MagicVar";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { transformPolicyRequest } from "../utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Menu from "@material-ui/core/Menu";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import { Delete, Edit } from "@material-ui/icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import { sizeToString } from "../../../utils";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import AddPolicy from "../Dialogs/AddPolicy";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Delete, Edit } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Menu from "@material-ui/core/Menu";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -77,8 +77,6 @@ function useQuery() {
|
|||
export default function Policy() {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "policy" });
|
||||
const classes = useStyles();
|
||||
// const [loading, setLoading] = useState(false);
|
||||
// const [tab, setTab] = useState(0);
|
||||
const [policies, setPolicies] = useState([]);
|
||||
const [statics, setStatics] = useState([]);
|
||||
const [page, setPage] = useState(1);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
const boolFields = ["IsOriginLinkEnable", "AutoRename", "IsPrivate"];
|
||||
|
||||
const numberFields = ["MaxSize"];
|
||||
|
||||
const boolFieldsInOptions = ["placeholder_with_size", "s3_path_style"];
|
||||
|
||||
const numberFieldsInOptions = ["chunk_size", "tps_limit", "tps_limit_burst"];
|
||||
const listJsonFieldsInOptions = ["file_type", "thumb_exts"];
|
||||
|
||||
|
|
@ -14,17 +17,17 @@ export const transformResponse = (response) => {
|
|||
);
|
||||
boolFieldsInOptions.forEach(
|
||||
(field) =>
|
||||
(response.data.OptionsSerialized[field] = response.data
|
||||
.OptionsSerialized[field]
|
||||
? "true"
|
||||
: "false")
|
||||
(response.data.OptionsSerialized[field] = response.data
|
||||
.OptionsSerialized[field]
|
||||
? "true"
|
||||
: "false")
|
||||
);
|
||||
numberFieldsInOptions.forEach(
|
||||
(field) =>
|
||||
(response.data.OptionsSerialized[field] = response.data
|
||||
.OptionsSerialized[field]
|
||||
? response.data.OptionsSerialized[field].toString()
|
||||
: 0)
|
||||
(response.data.OptionsSerialized[field] = response.data
|
||||
.OptionsSerialized[field]
|
||||
? response.data.OptionsSerialized[field].toString()
|
||||
: 0)
|
||||
);
|
||||
|
||||
listJsonFieldsInOptions.forEach((field) => {
|
||||
|
|
@ -45,14 +48,14 @@ export const transformPolicyRequest = (policyCopy) => {
|
|||
);
|
||||
boolFieldsInOptions.forEach(
|
||||
(field) =>
|
||||
(policyCopy.OptionsSerialized[field] =
|
||||
policyCopy.OptionsSerialized[field] === "true")
|
||||
(policyCopy.OptionsSerialized[field] =
|
||||
policyCopy.OptionsSerialized[field] === "true")
|
||||
);
|
||||
numberFieldsInOptions.forEach(
|
||||
(field) =>
|
||||
(policyCopy.OptionsSerialized[field] = parseInt(
|
||||
policyCopy.OptionsSerialized[field]
|
||||
))
|
||||
(policyCopy.OptionsSerialized[field] = parseInt(
|
||||
policyCopy.OptionsSerialized[field]
|
||||
))
|
||||
);
|
||||
|
||||
listJsonFieldsInOptions.forEach((field) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,422 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { lighten } from "@material-ui/core";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import { reportReasons } from "../../../config";
|
||||
import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import { formatLocalTime } from "../../../utils/datetime";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
[theme.breakpoints.up("md")]: {
|
||||
marginLeft: 100,
|
||||
},
|
||||
marginBottom: 40,
|
||||
},
|
||||
content: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
container: {
|
||||
overflowX: "auto",
|
||||
},
|
||||
tableContainer: {
|
||||
marginTop: 16,
|
||||
},
|
||||
header: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
headerRight: {},
|
||||
highlight:
|
||||
theme.palette.type === "light"
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85),
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark,
|
||||
},
|
||||
visuallyHidden: {
|
||||
border: 0,
|
||||
clip: "rect(0 0 0 0)",
|
||||
height: 1,
|
||||
margin: -1,
|
||||
overflow: "hidden",
|
||||
padding: 0,
|
||||
position: "absolute",
|
||||
top: 20,
|
||||
width: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function ReportList() {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const { t: tApp } = useTranslation("application");
|
||||
const { t: tDashboard } = useTranslation("dashboard");
|
||||
const classes = useStyles();
|
||||
const [reports, setReports] = useState([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [users, setUsers] = useState({});
|
||||
const [orderBy, setOrderBy] = useState(["id", "desc"]);
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [ids, setIds] = useState({});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const loadList = () => {
|
||||
API.post("/admin/report/list", {
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
order_by: orderBy.join(" "),
|
||||
})
|
||||
.then((response) => {
|
||||
setUsers(response.data.users);
|
||||
setReports(response.data.items);
|
||||
setTotal(response.data.total);
|
||||
setIds(response.data.ids);
|
||||
setSelected([]);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadList();
|
||||
}, [page, pageSize, orderBy]);
|
||||
|
||||
const deleteReport = (id) => {
|
||||
setLoading(true);
|
||||
API.post("/admin/report/delete", { id: [id] })
|
||||
.then(() => {
|
||||
loadList();
|
||||
ToggleSnackbar("top", "right", t("markSuccessful"), "success");
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const deleteShare = (id) => {
|
||||
setLoading(true);
|
||||
API.post("/admin/share/delete", { id: [id] })
|
||||
.then(() => {
|
||||
loadList();
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
tDashboard("share.deleted"),
|
||||
"success"
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const deleteBatch = () => {
|
||||
setLoading(true);
|
||||
API.post("/admin/report/delete", { id: selected })
|
||||
.then(() => {
|
||||
loadList();
|
||||
ToggleSnackbar("top", "right", t("markSuccessful"), "success");
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectAllClick = (event) => {
|
||||
if (event.target.checked) {
|
||||
const newSelecteds = reports.map((n) => n.ID);
|
||||
setSelected(newSelecteds);
|
||||
return;
|
||||
}
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleClick = (event, name) => {
|
||||
const selectedIndex = selected.indexOf(name);
|
||||
let newSelected = [];
|
||||
|
||||
if (selectedIndex === -1) {
|
||||
newSelected = newSelected.concat(selected, name);
|
||||
} else if (selectedIndex === 0) {
|
||||
newSelected = newSelected.concat(selected.slice(1));
|
||||
} else if (selectedIndex === selected.length - 1) {
|
||||
newSelected = newSelected.concat(selected.slice(0, -1));
|
||||
} else if (selectedIndex > 0) {
|
||||
newSelected = newSelected.concat(
|
||||
selected.slice(0, selectedIndex),
|
||||
selected.slice(selectedIndex + 1)
|
||||
);
|
||||
}
|
||||
|
||||
setSelected(newSelected);
|
||||
};
|
||||
|
||||
const isSelected = (id) => selected.indexOf(id) !== -1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes.header}>
|
||||
<div className={classes.headerRight}>
|
||||
<Button
|
||||
color={"primary"}
|
||||
onClick={() => loadList()}
|
||||
variant={"outlined"}
|
||||
>
|
||||
{tDashboard("policy.refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Paper square className={classes.tableContainer}>
|
||||
{selected.length > 0 && (
|
||||
<Toolbar className={classes.highlight}>
|
||||
<Typography
|
||||
style={{ flex: "1 1 100%" }}
|
||||
color="inherit"
|
||||
variant="subtitle1"
|
||||
>
|
||||
{tDashboard("user.selectedObjects", {
|
||||
num: selected.length,
|
||||
})}
|
||||
</Typography>
|
||||
<Tooltip title={t("markAsResolved")}>
|
||||
<IconButton
|
||||
onClick={deleteBatch}
|
||||
disabled={loading}
|
||||
aria-label="delete"
|
||||
>
|
||||
<CheckCircleOutlineIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Toolbar>
|
||||
)}
|
||||
<TableContainer className={classes.container}>
|
||||
<Table aria-label="sticky table" size={"small"}>
|
||||
<TableHead>
|
||||
<TableRow style={{ height: 52 }}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
indeterminate={
|
||||
selected.length > 0 &&
|
||||
selected.length < reports.length
|
||||
}
|
||||
checked={
|
||||
reports.length > 0 &&
|
||||
selected.length === reports.length
|
||||
}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all desserts",
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 10 }}>
|
||||
<TableSortLabel
|
||||
active={orderBy[0] === "id"}
|
||||
direction={orderBy[1]}
|
||||
onClick={() =>
|
||||
setOrderBy([
|
||||
"id",
|
||||
orderBy[1] === "asc"
|
||||
? "desc"
|
||||
: "asc",
|
||||
])
|
||||
}
|
||||
>
|
||||
#
|
||||
{orderBy[0] === "id" ? (
|
||||
<span
|
||||
className={
|
||||
classes.visuallyHidden
|
||||
}
|
||||
>
|
||||
{orderBy[1] === "desc"
|
||||
? "sorted descending"
|
||||
: "sorted ascending"}
|
||||
</span>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 130 }}>
|
||||
{t("reportedContent")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 90 }}>
|
||||
{tDashboard("policy.type")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 90 }}>
|
||||
{t("reason")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 150 }}>
|
||||
{t("description")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 100 }}>
|
||||
{tDashboard("vas.shareLink")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 150 }}>
|
||||
{t("reportTime")}
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 80 }}>
|
||||
{tDashboard("policy.actions")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{reports.map((row) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={row.ID}
|
||||
role="checkbox"
|
||||
selected={isSelected(row.ID)}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
onClick={(event) =>
|
||||
handleClick(event, row.ID)
|
||||
}
|
||||
checked={isSelected(row.ID)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{row.ID}</TableCell>
|
||||
<TableCell
|
||||
style={{ wordBreak: "break-all" }}
|
||||
>
|
||||
{row.Share.ID === 0 && t("invalid")}
|
||||
{row.Share.ID > 0 && (
|
||||
<Link
|
||||
target={"_blank"}
|
||||
color="inherit"
|
||||
href={
|
||||
"/s/" +
|
||||
ids[row.Share.ID] +
|
||||
(row.Share.Password === ""
|
||||
? ""
|
||||
: "?password=" +
|
||||
row.Share.Password)
|
||||
}
|
||||
>
|
||||
{row.Share.SourceName}
|
||||
</Link>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{row.Share.ID > 0 &&
|
||||
(row.Share.IsDir
|
||||
? tDashboard("share.folder")
|
||||
: tDashboard("share.file"))}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{tApp(reportReasons[row.Reason])}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{ wordBreak: "break-all" }}
|
||||
>
|
||||
{row.Description}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Link
|
||||
href={
|
||||
"/admin/user/edit/" +
|
||||
row.Share.UserID
|
||||
}
|
||||
>
|
||||
{users[row.Share.UserID]
|
||||
? users[row.Share.UserID].Nick
|
||||
: tDashboard(
|
||||
"file.unknownUploader"
|
||||
)}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{formatLocalTime(row.CreatedAt)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Tooltip title={t("markAsResolved")}>
|
||||
<IconButton
|
||||
disabled={loading}
|
||||
onClick={() =>
|
||||
deleteReport(row.ID)
|
||||
}
|
||||
size={"small"}
|
||||
>
|
||||
<CheckCircleOutlineIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{row.Share.ID > 0 && (
|
||||
<Tooltip title={t("deleteShare")}>
|
||||
<IconButton
|
||||
disabled={loading}
|
||||
onClick={() =>
|
||||
deleteShare(
|
||||
row.Share.ID
|
||||
)
|
||||
}
|
||||
size={"small"}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 25, 50, 100]}
|
||||
component="div"
|
||||
count={total}
|
||||
rowsPerPage={pageSize}
|
||||
page={page - 1}
|
||||
onChangePage={(e, p) => setPage(p + 1)}
|
||||
onChangeRowsPerPage={(e) => {
|
||||
setPageSize(e.target.value);
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import AlertDialog from "../Dialogs/Alert";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import FileSelector from "../Common/FileSelector";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -39,6 +42,7 @@ export default function Access() {
|
|||
const { t: tVas } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const classes = useStyles();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [initCompleted, setInitComplete] = useState(false);
|
||||
const [options, setOptions] = useState({
|
||||
register_enabled: "1",
|
||||
default_group: "1",
|
||||
|
|
@ -46,7 +50,14 @@ export default function Access() {
|
|||
login_captcha: "0",
|
||||
reg_captcha: "0",
|
||||
forget_captcha: "0",
|
||||
qq_login: "0",
|
||||
qq_direct_login: "0",
|
||||
qq_login_id: "",
|
||||
qq_login_key: "",
|
||||
authn_enabled: "0",
|
||||
mail_domain_filter: "0",
|
||||
mail_domain_filter_list: "",
|
||||
initial_files: "[]",
|
||||
});
|
||||
const [siteURL, setSiteURL] = useState("");
|
||||
const [groups, setGroups] = useState([]);
|
||||
|
|
@ -86,6 +97,7 @@ export default function Access() {
|
|||
setSiteURL(response.data.siteURL);
|
||||
delete response.data.siteURL;
|
||||
setOptions(response.data);
|
||||
setInitComplete(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
|
|
@ -306,6 +318,171 @@ export default function Access() {
|
|||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl>
|
||||
{initCompleted && (
|
||||
<FileSelector
|
||||
label={tVas("initialFiles")}
|
||||
value={JSON.parse(
|
||||
options.initial_files
|
||||
)}
|
||||
onChange={(v) =>
|
||||
handleInputChange("initial_files")({
|
||||
target: { value: v },
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("initialFilesDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("filterEmailProvider")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={options.mail_domain_filter}
|
||||
onChange={handleInputChange(
|
||||
"mail_domain_filter"
|
||||
)}
|
||||
required
|
||||
>
|
||||
{[
|
||||
tVas("filterEmailProviderDisabled"),
|
||||
tVas("filterEmailProviderWhitelist"),
|
||||
tVas("filterEmailProviderBlacklist"),
|
||||
].map((v, i) => (
|
||||
<MenuItem key={i} value={i.toString()}>
|
||||
{v}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("filterEmailProviderDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{options.mail_domain_filter !== "0" && (
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("filterEmailProviderRule")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={options.mail_domain_filter_list}
|
||||
onChange={handleChange(
|
||||
"mail_domain_filter_list"
|
||||
)}
|
||||
multiline
|
||||
rowsMax="10"
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("filterEmailProviderRuleDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.root}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{tVas("qqConnect")}
|
||||
</Typography>
|
||||
<div className={classes.formContainer}>
|
||||
<div className={classes.form}>
|
||||
<Alert severity="info">
|
||||
{tVas("qqConnectHint", {
|
||||
url: siteURL.endsWith("/")
|
||||
? siteURL + "login/qq"
|
||||
: siteURL + "/login/qq",
|
||||
})}
|
||||
</Alert>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.qq_login === "1"}
|
||||
onChange={handleChange("qq_login")}
|
||||
/>
|
||||
}
|
||||
label={tVas("enableQQConnect")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("enableQQConnectDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{options.qq_login === "1" && (
|
||||
<>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={
|
||||
options.qq_direct_login ===
|
||||
"1"
|
||||
}
|
||||
onChange={handleChange(
|
||||
"qq_direct_login"
|
||||
)}
|
||||
/>
|
||||
}
|
||||
label={tVas("loginWithoutBinding")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("loginWithoutBindingDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("appid")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
required
|
||||
value={options.qq_login_id}
|
||||
onChange={handleInputChange(
|
||||
"qq_login_id"
|
||||
)}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("appidDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("appKey")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
required
|
||||
value={options.qq_login_key}
|
||||
onChange={handleInputChange(
|
||||
"qq_login_key"
|
||||
)}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("appKeyDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import SizeInput from "../Common/SizeInput";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
|
|
@ -414,7 +414,7 @@ export default function ImageSetting() {
|
|||
<Typography variant="h6" gutterBottom>
|
||||
{t("thumbnails")}
|
||||
</Typography>
|
||||
<div className={classes.form}>
|
||||
<div className={classes.form}>
|
||||
<Alert severity="info">
|
||||
<Trans
|
||||
ns={"dashboard"}
|
||||
|
|
@ -428,7 +428,7 @@ export default function ImageSetting() {
|
|||
]}
|
||||
/>
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
{t("thumbnailBasic")}
|
||||
</Typography>
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -59,6 +59,7 @@ export default function Mail() {
|
|||
smtpPass: "",
|
||||
smtpEncryption: "",
|
||||
mail_keepalive: "30",
|
||||
over_used_template: "",
|
||||
mail_activation_template: "",
|
||||
mail_reset_pwd_template: "",
|
||||
});
|
||||
|
|
@ -382,6 +383,26 @@ export default function Mail() {
|
|||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("overuseReminder")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={options.over_used_template}
|
||||
onChange={handleChange(
|
||||
"over_used_template"
|
||||
)}
|
||||
multiline
|
||||
rowsMax="10"
|
||||
required
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("overuseReminderDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
|
|
|
|||
|
|
@ -1,17 +1,26 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import { green } from "@material-ui/core/colors";
|
||||
import { Cancel, CheckCircle, Sync } from "@material-ui/icons";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Tooltip } from "@material-ui/core";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
|
|
@ -34,22 +43,37 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
export default function SiteInformation() {
|
||||
const { t } = useTranslation("dashboard", { keyPrefix: "settings" });
|
||||
const { t: tVas } = useTranslation("dashboard", { keyPrefix: "vas" });
|
||||
const { t: tGlobal } = useTranslation("dashboard");
|
||||
const classes = useStyles();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [options, setOptions] = useState({
|
||||
siteURL: "",
|
||||
siteName: "",
|
||||
siteTitle: "",
|
||||
siteKeywords: "",
|
||||
siteDes: "",
|
||||
siteScript: "",
|
||||
siteNotice: "",
|
||||
pwa_small_icon: "",
|
||||
pwa_medium_icon: "",
|
||||
pwa_large_icon: "",
|
||||
pwa_display: "",
|
||||
pwa_theme_color: "",
|
||||
pwa_background_color: "",
|
||||
vol_content: "",
|
||||
show_app_promotion: "0",
|
||||
app_feedback_link: "",
|
||||
app_forum_link: "",
|
||||
});
|
||||
|
||||
const vol = useMemo(() => {
|
||||
if (options.vol_content) {
|
||||
const volJson = atob(options.vol_content);
|
||||
return JSON.parse(volJson);
|
||||
}
|
||||
}, [options]);
|
||||
|
||||
const handleChange = (name) => (event) => {
|
||||
setOptions({
|
||||
...options,
|
||||
|
|
@ -57,6 +81,17 @@ export default function SiteInformation() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleOptionChange = (name) => (event) => {
|
||||
let value = event.target.value;
|
||||
if (event.target.checked !== undefined) {
|
||||
value = event.target.checked ? "1" : "0";
|
||||
}
|
||||
setOptions({
|
||||
...options,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
|
|
@ -64,7 +99,7 @@ export default function SiteInformation() {
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const refresh = () =>
|
||||
API.post("/admin/setting", {
|
||||
keys: Object.keys(options),
|
||||
})
|
||||
|
|
@ -74,6 +109,9 @@ export default function SiteInformation() {
|
|||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
|
|
@ -101,6 +139,21 @@ export default function SiteInformation() {
|
|||
});
|
||||
};
|
||||
|
||||
const syncVol = () => {
|
||||
setLoading(true);
|
||||
API.get("/admin/vol/sync")
|
||||
.then(() => {
|
||||
refresh();
|
||||
ToggleSnackbar("top", "right", tVas("volSynced"), "success");
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={submit}>
|
||||
|
|
@ -138,6 +191,20 @@ export default function SiteInformation() {
|
|||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{t("siteKeywords")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={options.siteKeywords}
|
||||
onChange={handleChange("siteKeywords")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("siteKeywordsDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
|
|
@ -183,8 +250,149 @@ export default function SiteInformation() {
|
|||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{t("announcement")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
placeholder={t("supportHTML")}
|
||||
multiline
|
||||
value={options.siteNotice}
|
||||
onChange={handleChange("siteNotice")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{t("announcementDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.root}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{tVas("mobileApp")}
|
||||
</Typography>
|
||||
<div className={classes.formContainer}>
|
||||
<div className={classes.form}>
|
||||
<Alert severity="info">
|
||||
<Typography variant="body2">
|
||||
<Trans
|
||||
ns={"dashboard"}
|
||||
i18nKey={"vas.volPurchase"}
|
||||
components={[
|
||||
<Link
|
||||
key={0}
|
||||
href={
|
||||
"https://cloudreve.org/login"
|
||||
}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
<Link
|
||||
key={1}
|
||||
href={
|
||||
"https://cloudreve.org/ios"
|
||||
}
|
||||
target={"_blank"}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</Typography>
|
||||
</Alert>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("iosVol")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
{vol ? (
|
||||
<CheckCircle
|
||||
style={{
|
||||
color: green[500],
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Cancel color={"error"} />
|
||||
)}
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<Tooltip
|
||||
title={tVas("syncLicense")}
|
||||
>
|
||||
<IconButton
|
||||
disabled={loading}
|
||||
onClick={() => syncVol()}
|
||||
aria-label="toggle password visibility"
|
||||
>
|
||||
<Sync />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</InputAdornment>
|
||||
}
|
||||
readOnly
|
||||
value={
|
||||
vol ? vol.domain : tGlobal("share.none")
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={
|
||||
options.show_app_promotion ===
|
||||
"1"
|
||||
}
|
||||
onChange={handleOptionChange(
|
||||
"show_app_promotion"
|
||||
)}
|
||||
/>
|
||||
}
|
||||
label={tVas("showAppPromotion")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("showAppPromotionDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("appFeedback")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={options.app_feedback_link}
|
||||
onChange={handleChange("app_feedback_link")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("appLinkDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tVas("appForum")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
value={options.app_forum_link}
|
||||
onChange={handleChange("app_forum_link")}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text">
|
||||
{tVas("appLinkDes")}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.root}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{t("pwa")}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import CreateTheme from "../Dialogs/CreateTheme";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -123,7 +123,7 @@ export default function Theme() {
|
|||
const res = JSON.parse(options.themes);
|
||||
const themeString = {};
|
||||
|
||||
Object.keys(res).forEach((k) => {
|
||||
Object.keys(res).map((k) => {
|
||||
themeString[k] = JSON.stringify(res[k]);
|
||||
});
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ export default function Theme() {
|
|||
.secondary
|
||||
)
|
||||
) {
|
||||
throw e;
|
||||
throw "error";
|
||||
}
|
||||
setTheme({
|
||||
...theme,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import SizeInput from "../Common/SizeInput";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,29 +1,29 @@
|
|||
import { lighten } from "@material-ui/core";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Delete, FilterList } from "@material-ui/icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Delete, FilterList } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { lighten } from "@material-ui/core";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import ShareFilter from "../Dialogs/ShareFilter";
|
||||
import { formatLocalTime } from "../../../utils/datetime";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -93,6 +93,7 @@ export default function Share() {
|
|||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const loadList = () => {
|
||||
API.post("/admin/share/list", {
|
||||
page: page,
|
||||
|
|
@ -376,6 +377,36 @@ export default function Share() {
|
|||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{ minWidth: 100 }}
|
||||
align={"right"}
|
||||
>
|
||||
<TableSortLabel
|
||||
active={orderBy[0] === "score"}
|
||||
direction={orderBy[1]}
|
||||
onClick={() =>
|
||||
setOrderBy([
|
||||
"score",
|
||||
orderBy[1] === "asc"
|
||||
? "desc"
|
||||
: "asc",
|
||||
])
|
||||
}
|
||||
>
|
||||
{t("price")}
|
||||
{orderBy[0] === "score" ? (
|
||||
<span
|
||||
className={
|
||||
classes.visuallyHidden
|
||||
}
|
||||
>
|
||||
{orderBy[1] === "desc"
|
||||
? "sorted descending"
|
||||
: "sorted ascending"}
|
||||
</span>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell style={{ minWidth: 120 }}>
|
||||
{t("autoExpire")}
|
||||
</TableCell>
|
||||
|
|
@ -436,6 +467,9 @@ export default function Share() {
|
|||
<TableCell align={"right"}>
|
||||
{row.Downloads}
|
||||
</TableCell>
|
||||
<TableCell align={"right"}>
|
||||
{row.Score}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{row.RemainDownloads > -1 &&
|
||||
t("afterNDownloads", {
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
import { lighten } from "@material-ui/core";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { sizeToString } from "../../../utils";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { lighten } from "@material-ui/core";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import ShareFilter from "../Dialogs/ShareFilter";
|
||||
import { sizeToString } from "../../../utils";
|
||||
import { formatLocalTime } from "../../../utils/datetime";
|
||||
import Aria2Helper from "./Aria2Helper";
|
||||
import HelpIcon from "@material-ui/icons/Help";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import { lighten } from "@material-ui/core";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { getTaskProgress, getTaskStatus, getTaskType } from "../../../config";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { lighten } from "@material-ui/core";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import ShareFilter from "../Dialogs/ShareFilter";
|
||||
import { getTaskProgress, getTaskStatus, getTaskType } from "../../../config";
|
||||
import { formatLocalTime } from "../../../utils/datetime";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default function EditUserPreload() {
|
|||
API.get("/admin/user/" + id)
|
||||
.then((response) => {
|
||||
// 整型转换
|
||||
["Status", "GroupID"].forEach((v) => {
|
||||
["Status", "GroupID", "Score"].forEach((v) => {
|
||||
response.data[v] = response.data[v].toString();
|
||||
});
|
||||
setUser(response.data);
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
import { lighten } from "@material-ui/core";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Block, Delete, Edit, FilterList } from "@material-ui/icons";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import { sizeToString } from "../../../utils";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import { useHistory } from "react-router";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { Block, Delete, Edit, FilterList } from "@material-ui/icons";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { lighten } from "@material-ui/core";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import UserFilter from "../Dialogs/UserFilter";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React, { useCallback, useEffect,useMemo, useState } from "react";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import Input from "@material-ui/core/Input";
|
||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { useHistory } from "react-router";
|
||||
import { toggleSnackbar } from "../../../redux/explorer";
|
||||
import API from "../../../middleware/Api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -47,6 +47,7 @@ export default function UserForm(props) {
|
|||
Password: "", // 为空时只读
|
||||
Status: "0", // 转换类型
|
||||
GroupID: "2", // 转换类型
|
||||
Score: "0", // 转换类型
|
||||
TwoFactor: "",
|
||||
}
|
||||
);
|
||||
|
|
@ -222,6 +223,24 @@ export default function UserForm(props) {
|
|||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
{tDashboard("vas.credits")}
|
||||
</InputLabel>
|
||||
<Input
|
||||
type={"number"}
|
||||
inputProps={{
|
||||
min: 0,
|
||||
step: 1,
|
||||
}}
|
||||
value={user.Score}
|
||||
onChange={handleChange("Score")}
|
||||
required
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className={classes.form}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel htmlFor="component-helper">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { Button, IconButton, Typography, withStyles } from "@material-ui/core";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import API from "../../middleware/Api";
|
||||
import { Button, IconButton, Typography, withStyles } from "@material-ui/core";
|
||||
import DownloadingCard from "./DownloadingCard";
|
||||
import FinishedCard from "./FinishedCard";
|
||||
import RemoteDownloadButton from "../Dial/Aria2";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import Nothing from "../Placeholder/Nothing";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import React, { useCallback, useEffect,useMemo } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
|
@ -9,33 +10,32 @@ import {
|
|||
Typography,
|
||||
useTheme,
|
||||
} from "@material-ui/core";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { hex2bin, sizeToString } from "../../utils";
|
||||
import PermMediaIcon from "@material-ui/icons/PermMedia";
|
||||
import TypeIcon from "../FileManager/TypeIcon";
|
||||
import MuiExpansionPanel from "@material-ui/core/ExpansionPanel";
|
||||
import MuiExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
|
||||
import MuiExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import MuiExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
|
||||
import withStyles from "@material-ui/core/styles/withStyles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import { ExpandMore, HighlightOff } from "@material-ui/icons";
|
||||
import classNames from "classnames";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import Badge from "@material-ui/core/Badge";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import { ExpandMore, HighlightOff } from "@material-ui/icons";
|
||||
import PermMediaIcon from "@material-ui/icons/PermMedia";
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import TimeAgo from "timeago-react";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import API from "../../middleware/Api";
|
||||
import { hex2bin, sizeToString } from "../../utils";
|
||||
import TypeIcon from "../FileManager/TypeIcon";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import TimeAgo from "timeago-react";
|
||||
import SelectFileDialog from "../Modals/SelectFile";
|
||||
import { useHistory } from "react-router";
|
||||
import { TableVirtuoso } from "react-virtuoso";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TableVirtuoso } from "react-virtuoso";
|
||||
|
||||
const ExpansionPanel = withStyles({
|
||||
root: {
|
||||
|
|
|
|||
|
|
@ -184,13 +184,6 @@ export default function FinishedCard(props) {
|
|||
setExpanded(!!newExpanded);
|
||||
};
|
||||
|
||||
const getPercent = (completed, total) => {
|
||||
if (total === 0) {
|
||||
return 0;
|
||||
}
|
||||
return (completed / total) * 100;
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
setLoading(true);
|
||||
API.delete("/aria2/task/" + props.task.gid)
|
||||
|
|
@ -205,6 +198,13 @@ export default function FinishedCard(props) {
|
|||
});
|
||||
};
|
||||
|
||||
const getPercent = (completed, total) => {
|
||||
if (total == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (completed / total) * 100;
|
||||
};
|
||||
|
||||
const getDownloadName = useCallback(() => {
|
||||
return props.task.name === "." ? t("unknownTaskName") : props.task.name;
|
||||
}, [props.task.name]);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,25 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import { isCompressFile, isPreviewable, isTorrent } from "../../config";
|
||||
import UploadIcon from "@material-ui/icons/CloudUpload";
|
||||
import DownloadIcon from "@material-ui/icons/CloudDownload";
|
||||
import NewFolderIcon from "@material-ui/icons/CreateNewFolder";
|
||||
import OpenFolderIcon from "@material-ui/icons/FolderOpen";
|
||||
import FileCopyIcon from "@material-ui/icons/FileCopy";
|
||||
import ShareIcon from "@material-ui/icons/Share";
|
||||
import RenameIcon from "@material-ui/icons/BorderColor";
|
||||
import MoveIcon from "@material-ui/icons/Input";
|
||||
import LinkIcon from "@material-ui/icons/InsertLink";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import OpenIcon from "@material-ui/icons/OpenInNew";
|
||||
import {
|
||||
FolderDownload,
|
||||
FilePlus,
|
||||
FolderUpload,
|
||||
MagnetOn,
|
||||
Transfer,
|
||||
} from "mdi-material-ui";
|
||||
import {
|
||||
Divider,
|
||||
ListItemIcon,
|
||||
|
|
@ -5,32 +27,11 @@ import {
|
|||
Typography,
|
||||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
import Menu from "@material-ui/core/Menu";
|
||||
import { Archive, InfoOutlined, Unarchive } from "@material-ui/icons";
|
||||
import RenameIcon from "@material-ui/icons/BorderColor";
|
||||
import DownloadIcon from "@material-ui/icons/CloudDownload";
|
||||
import UploadIcon from "@material-ui/icons/CloudUpload";
|
||||
import NewFolderIcon from "@material-ui/icons/CreateNewFolder";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import FileCopyIcon from "@material-ui/icons/FileCopy";
|
||||
import OpenFolderIcon from "@material-ui/icons/FolderOpen";
|
||||
import MoveIcon from "@material-ui/icons/Input";
|
||||
import LinkIcon from "@material-ui/icons/InsertLink";
|
||||
import OpenIcon from "@material-ui/icons/OpenInNew";
|
||||
import ShareIcon from "@material-ui/icons/Share";
|
||||
import {
|
||||
FolderDownload,
|
||||
FolderUpload,
|
||||
MagnetOn,
|
||||
FilePlus,
|
||||
} from "mdi-material-ui";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { isCompressFile, isPreviewable, isTorrent } from "../../config";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import pathHelper from "../../utils/page";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import { Archive, InfoOutlined, Unarchive } from "@material-ui/icons";
|
||||
import Menu from "@material-ui/core/Menu";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import {
|
||||
batchGetSource,
|
||||
|
|
@ -54,6 +55,7 @@ import {
|
|||
openLoadingDialog,
|
||||
openMoveDialog,
|
||||
openMusicDialog,
|
||||
openRelocateDialog,
|
||||
openRemoteDownloadDialog,
|
||||
openRemoveDialog,
|
||||
openRenameDialog,
|
||||
|
|
@ -160,6 +162,9 @@ const mapDispatchToProps = (dispatch) => {
|
|||
refreshFileList: () => {
|
||||
dispatch(refreshFileList());
|
||||
},
|
||||
openRelocateDialog: () => {
|
||||
dispatch(openRelocateDialog());
|
||||
},
|
||||
openPreview: (share) => {
|
||||
dispatch(openPreview(share));
|
||||
},
|
||||
|
|
@ -611,6 +616,22 @@ class ContextMenuCompoment extends Component {
|
|||
</MenuItem>
|
||||
)}
|
||||
|
||||
{isHomePage && user.group.relocate && (
|
||||
<MenuItem
|
||||
dense
|
||||
onClick={() =>
|
||||
this.props.openRelocateDialog()
|
||||
}
|
||||
>
|
||||
<StyledListItemIcon>
|
||||
<Transfer />
|
||||
</StyledListItemIcon>
|
||||
<Typography variant="inherit">
|
||||
{t("vas.migrateStoragePolicy")}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{!this.props.isMultiple && isHomePage && (
|
||||
<MenuItem
|
||||
dense
|
||||
|
|
|
|||
|
|
@ -1,3 +1,15 @@
|
|||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import explorer, {
|
||||
changeContextMenu,
|
||||
openRemoveDialog,
|
||||
setSelectedTarget,
|
||||
} from "../../redux/explorer";
|
||||
import ObjectIcon from "./ObjectIcon";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
import classNames from "classnames";
|
||||
import ImgPreivew from "./ImgPreview";
|
||||
import pathHelper from "../../utils/page";
|
||||
import { isMac } from "../../utils";
|
||||
import {
|
||||
CircularProgress,
|
||||
Grid,
|
||||
|
|
@ -9,20 +21,8 @@ import {
|
|||
TableRow,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { configure, GlobalHotKeys } from "react-hotkeys";
|
||||
import explorer, {
|
||||
changeContextMenu,
|
||||
openRemoveDialog,
|
||||
setSelectedTarget,
|
||||
} from "../../redux/explorer";
|
||||
import { isMac } from "../../utils";
|
||||
import pathHelper from "../../utils/page";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
import ImgPreivew from "./ImgPreview";
|
||||
import ObjectIcon from "./ObjectIcon";
|
||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||
import Nothing from "../Placeholder/Nothing";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useLocation } from "react-router";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import classNames from "classnames";
|
||||
import { LazyLoadImage } from "react-lazy-load-image-component";
|
||||
import ContentLoader from "react-content-loader";
|
||||
import { baseURL } from "../../middleware/Api";
|
||||
import {
|
||||
ButtonBase,
|
||||
Divider,
|
||||
|
|
@ -6,18 +13,11 @@ import {
|
|||
Typography,
|
||||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
import classNames from "classnames";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { Component } from "react";
|
||||
import ContentLoader from "react-content-loader";
|
||||
import { LazyLoadImage } from "react-lazy-load-image-component";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import { baseURL } from "../../middleware/Api";
|
||||
import pathHelper from "../../utils/page";
|
||||
import TypeIcon from "./TypeIcon";
|
||||
import CheckCircleRoundedIcon from "@material-ui/icons/CheckCircleRounded";
|
||||
import { withRouter } from "react-router";
|
||||
import pathHelper from "../../utils/page";
|
||||
import statusHelper from "../../utils/page";
|
||||
import CheckCircleRoundedIcon from "@material-ui/icons/CheckCircleRounded";
|
||||
import Grow from "@material-ui/core/Grow";
|
||||
import FileName from "./FileName";
|
||||
|
||||
|
|
@ -187,53 +187,53 @@ class FileIconCompoment extends Component {
|
|||
)}
|
||||
>
|
||||
{this.props.file.thumb && !this.state.showPicIcon && (
|
||||
<div className={classes.preview}>
|
||||
<LazyLoadImage
|
||||
className={classNames(
|
||||
{
|
||||
[classes.hide]: this.state.loading,
|
||||
[classes.picPreview]:
|
||||
!this.state.loading,
|
||||
},
|
||||
classes.noDrag
|
||||
)}
|
||||
src={
|
||||
baseURL +
|
||||
(isSharePage && this.props.shareInfo
|
||||
? "/share/thumb/" +
|
||||
this.props.shareInfo.key +
|
||||
"/" +
|
||||
this.props.file.id +
|
||||
"?path=" +
|
||||
encodeURIComponent(
|
||||
this.props.file.path
|
||||
)
|
||||
: "/file/thumb/" + this.props.file.id)
|
||||
}
|
||||
afterLoad={() =>
|
||||
this.setState({ loading: false })
|
||||
}
|
||||
beforeLoad={() =>
|
||||
this.setState({ loading: true })
|
||||
}
|
||||
onError={() =>
|
||||
this.setState({ showPicIcon: true })
|
||||
}
|
||||
/>
|
||||
<ContentLoader
|
||||
height={150}
|
||||
width={170}
|
||||
className={classNames(
|
||||
{
|
||||
[classes.hide]: !this.state.loading,
|
||||
},
|
||||
classes.loadingAnimation
|
||||
)}
|
||||
>
|
||||
<div className={classes.preview}>
|
||||
<LazyLoadImage
|
||||
className={classNames(
|
||||
{
|
||||
[classes.hide]: this.state.loading,
|
||||
[classes.picPreview]:
|
||||
!this.state.loading,
|
||||
},
|
||||
classes.noDrag
|
||||
)}
|
||||
src={
|
||||
baseURL +
|
||||
(isSharePage && this.props.shareInfo
|
||||
? "/share/thumb/" +
|
||||
this.props.shareInfo.key +
|
||||
"/" +
|
||||
this.props.file.id +
|
||||
"?path=" +
|
||||
encodeURIComponent(
|
||||
this.props.file.path
|
||||
)
|
||||
: "/file/thumb/" + this.props.file.id)
|
||||
}
|
||||
afterLoad={() =>
|
||||
this.setState({ loading: false })
|
||||
}
|
||||
beforeLoad={() =>
|
||||
this.setState({ loading: true })
|
||||
}
|
||||
onError={() =>
|
||||
this.setState({ showPicIcon: true })
|
||||
}
|
||||
/>
|
||||
<ContentLoader
|
||||
height={150}
|
||||
width={170}
|
||||
className={classNames(
|
||||
{
|
||||
[classes.hide]: !this.state.loading,
|
||||
},
|
||||
classes.loadingAnimation
|
||||
)}
|
||||
>
|
||||
<rect x="0" y="0" width="100%" height="150" />
|
||||
</ContentLoader>
|
||||
</div>
|
||||
)}
|
||||
</ContentLoader>
|
||||
</div>
|
||||
)}
|
||||
{(!this.props.file.thumb || this.state.showPicIcon) && (
|
||||
<div className={classes.previewIcon}>
|
||||
<TypeIcon
|
||||
|
|
@ -274,8 +274,7 @@ class FileIconCompoment extends Component {
|
|||
variant="body2"
|
||||
className={classNames(classes.folderName, {
|
||||
[classes.folderNameSelected]: isSelected,
|
||||
[classes.folderNameNotSelected]:
|
||||
!isSelected,
|
||||
[classes.folderNameNotSelected]: !isSelected,
|
||||
[classes.shareFix]: this.props.share,
|
||||
})}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import React, { Component } from "react";
|
||||
|
||||
import Navigator from "./Navigator/Navigator";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import HTML5Backend from "react-dnd-html5-backend";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { changeSubTitle } from "../../redux/viewUpdate/action";
|
||||
import pathHelper from "../../utils/page";
|
||||
import DragLayer from "./DnD/DragLayer";
|
||||
import Explorer from "./Explorer";
|
||||
import Modals from "./Modals";
|
||||
import Navigator from "./Navigator/Navigator";
|
||||
import { connect } from "react-redux";
|
||||
import { changeSubTitle } from "../../redux/viewUpdate/action";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import pathHelper from "../../utils/page";
|
||||
import SideDrawer from "./Sidebar/SideDrawer";
|
||||
import classNames from "classnames";
|
||||
//import { ImageLoader } from "@abslant/cd-image-loader";
|
||||
import {
|
||||
closeAllModals,
|
||||
navigateTo,
|
||||
|
|
@ -69,6 +71,7 @@ class FileManager extends Component {
|
|||
super(props);
|
||||
this.image = React.createRef();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.setSelectedTarget([]);
|
||||
this.props.closeAllModals();
|
||||
|
|
@ -80,6 +83,7 @@ class FileManager extends Component {
|
|||
this.props.changeSubTitle(null);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ import CopyDialog from "../Modals/Copy";
|
|||
import DirectoryDownloadDialog from "../Modals/DirectoryDownload";
|
||||
import CreatShare from "../Modals/CreateShare";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import PurchaseShareDialog from "../Modals/PurchaseShare";
|
||||
import DecompressDialog from "../Modals/Decompress";
|
||||
import CompressDialog from "../Modals/Compress";
|
||||
import RelocateDialog from "../Modals/Relocate";
|
||||
import {
|
||||
closeAllModals,
|
||||
openLoadingDialog,
|
||||
|
|
@ -30,7 +32,6 @@ import {
|
|||
toggleSnackbar,
|
||||
} from "../../redux/explorer";
|
||||
import OptionSelector from "../Modals/OptionSelector";
|
||||
import { getDownloadURL } from "../../services/file";
|
||||
import { Trans, withTranslation } from "react-i18next";
|
||||
import RemoteDownload from "../Modals/RemoteDownload";
|
||||
import Delete from "../Modals/Delete";
|
||||
|
|
@ -128,16 +129,25 @@ class ModalsCompoment extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
scoreHandler = (callback) => {
|
||||
callback();
|
||||
};
|
||||
|
||||
Download = () => {
|
||||
getDownloadURL(this.props.selected[0])
|
||||
.then((response) => {
|
||||
window.location.assign(response.data);
|
||||
submitResave = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.setModalsLoading(true);
|
||||
API.post("/share/save/" + window.shareKey, {
|
||||
path:
|
||||
this.state.selectedPath === "//"
|
||||
? "/"
|
||||
: this.state.selectedPath,
|
||||
})
|
||||
.then(() => {
|
||||
this.onClose();
|
||||
this.downloaded = true;
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
this.props.t("vas.fileSaved"),
|
||||
"success"
|
||||
);
|
||||
this.props.refreshFileList();
|
||||
this.props.setModalsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.props.toggleSnackbar(
|
||||
|
|
@ -146,7 +156,7 @@ class ModalsCompoment extends Component {
|
|||
error.message,
|
||||
"error"
|
||||
);
|
||||
this.onClose();
|
||||
this.props.setModalsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -419,6 +429,7 @@ class ModalsCompoment extends Component {
|
|||
<div>
|
||||
<Loading />
|
||||
<OptionSelector />
|
||||
<PurchaseShareDialog />
|
||||
<Dialog
|
||||
open={this.props.modalsStatus.getSource}
|
||||
onClose={this.onClose}
|
||||
|
|
@ -664,6 +675,57 @@ class ModalsCompoment extends Component {
|
|||
</div>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Dialog
|
||||
open={this.props.modalsStatus.resave}
|
||||
onClose={this.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{t("modals.saveToTitle")}
|
||||
</DialogTitle>
|
||||
<PathSelector
|
||||
presentPath={this.props.path}
|
||||
selected={this.props.selected}
|
||||
onSelect={this.setMoveTarget}
|
||||
/>
|
||||
|
||||
{this.state.selectedPath !== "" && (
|
||||
<DialogContent className={classes.contentFix}>
|
||||
<DialogContentText>
|
||||
<Trans
|
||||
i18nKey="modals.saveToTitleDescription"
|
||||
values={{
|
||||
name: this.state.selectedPathName,
|
||||
}}
|
||||
components={[<strong key={0} />]}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
)}
|
||||
<DialogActions>
|
||||
<Button onClick={this.onClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
onClick={this.submitResave}
|
||||
color="primary"
|
||||
disabled={
|
||||
this.state.selectedPath === "" ||
|
||||
this.props.modalsLoading
|
||||
}
|
||||
>
|
||||
{t("ok", { ns: "common" })}
|
||||
{this.props.modalsLoading && (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
className={classes.buttonProgress}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Delete
|
||||
open={this.props.modalsStatus.remove}
|
||||
onClose={this.onClose}
|
||||
|
|
@ -702,6 +764,12 @@ class ModalsCompoment extends Component {
|
|||
selected={this.props.selected}
|
||||
modalsLoading={this.props.modalsLoading}
|
||||
/>
|
||||
<RelocateDialog
|
||||
open={this.props.modalsStatus.relocate}
|
||||
onClose={this.onClose}
|
||||
selected={this.props.selected}
|
||||
modalsLoading={this.props.modalsLoading}
|
||||
/>
|
||||
<DirectoryDownloadDialog
|
||||
open={this.props.modalsStatus.directoryDownloading}
|
||||
onClose={this.onClose}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import {
|
||||
Button,
|
||||
Card,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
List,
|
||||
Popover,
|
||||
Slider,
|
||||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
|
|
@ -18,6 +20,10 @@ import PlayArrow from "@material-ui/icons/PlayArrow";
|
|||
import PlayNext from "@material-ui/icons/SkipNext";
|
||||
import PlayPrev from "@material-ui/icons/SkipPrevious";
|
||||
import Pause from "@material-ui/icons/Pause";
|
||||
import VolumeUp from '@material-ui/icons/VolumeUp';
|
||||
import VolumeDown from '@material-ui/icons/VolumeDown';
|
||||
import VolumeMute from '@material-ui/icons/VolumeMute';
|
||||
import VolumeOff from '@material-ui/icons/VolumeOff';
|
||||
import { Repeat, RepeatOne, Shuffle } from "@material-ui/icons";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { Component } from "react";
|
||||
|
|
@ -45,6 +51,11 @@ const styles = (theme) => ({
|
|||
slider_root: {
|
||||
"vertical-align": "middle",
|
||||
},
|
||||
setvol: {
|
||||
width: 200,
|
||||
height: 28,
|
||||
"line-height": "42px",
|
||||
},
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
|
|
@ -75,11 +86,14 @@ class MusicPlayerComponent extends Component {
|
|||
items: [],
|
||||
currentIndex: 0,
|
||||
//isOpen: false,
|
||||
//isPlay:false,
|
||||
isPlay: false,
|
||||
currentTime: 0,
|
||||
duration: 0,
|
||||
progressText: "00:00/00:00",
|
||||
looptype: 0,
|
||||
volume: 0.8,
|
||||
openPropEl: null,
|
||||
mute: false,
|
||||
};
|
||||
myAudioRef = React.createRef();
|
||||
|
||||
|
|
@ -201,6 +215,7 @@ class MusicPlayerComponent extends Component {
|
|||
};
|
||||
|
||||
readyPlay = () => {
|
||||
this.myAudioRef.current.volume = this.state.volume;
|
||||
this.play();
|
||||
};
|
||||
|
||||
|
|
@ -227,9 +242,9 @@ class MusicPlayerComponent extends Component {
|
|||
|
||||
play = () => {
|
||||
this.myAudioRef.current.play();
|
||||
/*this.setState({
|
||||
this.setState({
|
||||
isPlay: true
|
||||
});*/
|
||||
});
|
||||
this.props.audioPreviewSetPlaying(
|
||||
this.state.items[this.state.currentIndex].intro,
|
||||
false
|
||||
|
|
@ -240,9 +255,9 @@ class MusicPlayerComponent extends Component {
|
|||
if (this.myAudioRef.current) {
|
||||
this.myAudioRef.current.pause();
|
||||
}
|
||||
/*this.setState({
|
||||
this.setState({
|
||||
isPlay: false
|
||||
})*/
|
||||
})
|
||||
this.props.audioPreviewSetPlaying(
|
||||
this.state.items[this.state.currentIndex]?.intro,
|
||||
true
|
||||
|
|
@ -417,18 +432,9 @@ class MusicPlayerComponent extends Component {
|
|||
<IconButton
|
||||
edge="end"
|
||||
aria-label=""
|
||||
onClick={this.pause}
|
||||
onClick={this.playOrPaues}
|
||||
>
|
||||
<Pause />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label=""
|
||||
onClick={this.play}
|
||||
>
|
||||
<PlayArrow />
|
||||
{this.state.isPlay ? (<Pause />) : (<PlayArrow />)}
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
|
@ -440,6 +446,58 @@ class MusicPlayerComponent extends Component {
|
|||
<PlayNext />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label=""
|
||||
onClick={(e) => { this.setState({ openPropEl: e.currentTarget }) }}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
if (this.state.mute) {
|
||||
this.setState({ mute: false });
|
||||
this.myAudioRef.current.muted = false;
|
||||
} else {
|
||||
this.setState({ mute: true });
|
||||
this.myAudioRef.current.muted = true;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{this.state.mute ? (
|
||||
<VolumeOff />
|
||||
) : this.state.volume >= 0.7 ? (
|
||||
<VolumeUp />
|
||||
) : this.state.volume <= 0.3 ? (
|
||||
<VolumeMute />
|
||||
) : (
|
||||
<VolumeDown />
|
||||
)}
|
||||
</IconButton>
|
||||
<Popover
|
||||
id="volume-controller"
|
||||
open={Boolean(this.state.openPropEl)}
|
||||
anchorEl={this.state.openPropEl}
|
||||
onClose={() => { this.setState({ openPropEl: null }) }}
|
||||
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
||||
transformOrigin={{ vertical: "bottom", horizontal: "center" }}
|
||||
>
|
||||
<Card className={classes.setvol}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item><VolumeDown /></Grid>
|
||||
<Grid item xs><Slider
|
||||
aria-labelledby="continuous-slider"
|
||||
value={this.state.volume}
|
||||
min={0} max={1} step={0.01} defaultValue={this.state.volume}
|
||||
onChange={(e, vol) => {
|
||||
this.setState({ volume: vol });
|
||||
this.myAudioRef.current.volume = vol;
|
||||
}}
|
||||
style={{ padding: "13px 0" }}
|
||||
/></Grid>
|
||||
<Grid item><VolumeUp /></Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</Popover>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export default function DropDown(props) {
|
|||
return (
|
||||
<>
|
||||
{props.folders.map((folder, id) => (
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<DropDownItem
|
||||
key={id}
|
||||
path={"/" + props.folders.slice(0, id).join("/")}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import DropDown from "./DropDown";
|
|||
import pathHelper from "../../../utils/page";
|
||||
import classNames from "classnames";
|
||||
import Auth from "../../../middleware/Auth";
|
||||
import Avatar from "@material-ui/core/Avatar";
|
||||
import { Archive } from "@material-ui/icons";
|
||||
import { FilePlus } from "mdi-material-ui";
|
||||
import SubActions from "./SubActions";
|
||||
|
|
@ -371,6 +370,7 @@ class NavigatorComponent extends Component {
|
|||
</ListItemIcon>
|
||||
{t("fileManager.newFolder")}
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={() => this.performAction("newFile")}>
|
||||
<ListItemIcon>
|
||||
<FilePlus />
|
||||
|
|
@ -390,6 +390,7 @@ class NavigatorComponent extends Component {
|
|||
},
|
||||
classes.container
|
||||
)}
|
||||
id={"drag-layer-inherit"}
|
||||
>
|
||||
<div className={classes.navigatorContainer}>
|
||||
<div className={classes.nav} ref={this.element}>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,21 @@ import ViewListIcon from "@material-ui/icons/ViewList";
|
|||
import ViewSmallIcon from "@material-ui/icons/ViewComfy";
|
||||
import ViewModuleIcon from "@material-ui/icons/ViewModule";
|
||||
import DownloadIcon from "@material-ui/icons/CloudDownload";
|
||||
import SaveIcon from "@material-ui/icons/Save";
|
||||
import ReportIcon from "@material-ui/icons/Report";
|
||||
import Avatar from "@material-ui/core/Avatar";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Auth from "../../../middleware/Auth";
|
||||
import { changeViewMethod, setShareUserPopover } from "../../../redux/explorer";
|
||||
import { changeSortMethod, startBatchDownload } from "../../../redux/explorer/action";
|
||||
import {
|
||||
changeViewMethod,
|
||||
openResaveDialog,
|
||||
setShareUserPopover,
|
||||
} from "../../../redux/explorer";
|
||||
import { FormatPageBreak } from "mdi-material-ui";
|
||||
import pathHelper from "../../../utils/page";
|
||||
import { changePageSize } from "../../../redux/viewUpdate/action";
|
||||
import Report from "../../Modals/Report";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Sort from "../Sort";
|
||||
|
||||
|
|
@ -22,6 +29,17 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// const sortOptions = [
|
||||
// "A-Z",
|
||||
// "Z-A",
|
||||
// "oldestUploaded",
|
||||
// "newestUploaded",
|
||||
// "oldestModified",
|
||||
// "newestModified",
|
||||
// "smallest",
|
||||
// "largest",
|
||||
// ];
|
||||
|
||||
const paginationOption = ["50", "100", "200", "500", "1000"];
|
||||
|
||||
export default function SubActions({ isSmall, inherit }) {
|
||||
|
|
@ -41,6 +59,10 @@ export default function SubActions({ isSmall, inherit }) {
|
|||
(method) => dispatch(changeSortMethod(method)),
|
||||
[dispatch]
|
||||
);
|
||||
const OpenResaveDialog = useCallback(
|
||||
(key) => dispatch(openResaveDialog(key)),
|
||||
[dispatch]
|
||||
);
|
||||
const SetShareUserPopover = useCallback(
|
||||
(e) => dispatch(setShareUserPopover(e)),
|
||||
[dispatch]
|
||||
|
|
@ -52,7 +74,13 @@ export default function SubActions({ isSmall, inherit }) {
|
|||
const ChangePageSize = useCallback((e) => dispatch(changePageSize(e)), [
|
||||
dispatch,
|
||||
]);
|
||||
// const [anchorSort, setAnchorSort] = useState(null);
|
||||
const [anchorPagination, setAnchorPagination] = useState(null);
|
||||
// const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [openReport, setOpenReport] = useState(false);
|
||||
// const showSortOptions = (e) => {
|
||||
// setAnchorSort(e.currentTarget);
|
||||
// };
|
||||
const showPaginationOptions = (e) => {
|
||||
setAnchorPagination(e.currentTarget);
|
||||
};
|
||||
|
|
@ -71,8 +99,8 @@ export default function SubActions({ isSmall, inherit }) {
|
|||
viewMethod === "icon"
|
||||
? "list"
|
||||
: viewMethod === "list"
|
||||
? "smallIcon"
|
||||
: "icon";
|
||||
? "smallIcon"
|
||||
: "icon";
|
||||
Auth.SetPreference("view_method", newMethod);
|
||||
OpenLoadingDialog(newMethod);
|
||||
};
|
||||
|
|
@ -161,6 +189,37 @@ export default function SubActions({ isSmall, inherit }) {
|
|||
className={classes.sideButton}
|
||||
onChange={onChangeSort}
|
||||
/>
|
||||
|
||||
{share && (
|
||||
<>
|
||||
<IconButton
|
||||
title={vasT("saveToMyFiles")}
|
||||
className={classes.sideButton}
|
||||
onClick={() => OpenResaveDialog(share.key)}
|
||||
color={inherit ? "inherit" : "default"}
|
||||
>
|
||||
<SaveIcon fontSize={isSmall ? "small" : "default"} />
|
||||
</IconButton>
|
||||
{!inherit && (
|
||||
<>
|
||||
<IconButton
|
||||
title={vasT("report")}
|
||||
className={classes.sideButton}
|
||||
onClick={() => setOpenReport(true)}
|
||||
>
|
||||
<ReportIcon
|
||||
fontSize={isSmall ? "small" : "default"}
|
||||
/>
|
||||
</IconButton>
|
||||
<Report
|
||||
open={openReport}
|
||||
share={share}
|
||||
onClose={() => setOpenReport(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{share && (
|
||||
<IconButton
|
||||
title={t("shareCreateBy", { nick: share.creator.nick })}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ const styles = (theme) => ({
|
|||
padding: "0",
|
||||
},
|
||||
});
|
||||
|
||||
class PathSelectorCompoment extends Component {
|
||||
state = {
|
||||
presentPath: "/",
|
||||
|
|
@ -110,6 +111,7 @@ class PathSelectorCompoment extends Component {
|
|||
this.sourceDirList = dirList
|
||||
this.setState({
|
||||
presentPath: toBeLoad,
|
||||
dirList: dirList,
|
||||
selectedTarget: null,
|
||||
}, this.updateDirList);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -149,6 +149,14 @@ export default function SideDrawer() {
|
|||
value: (d, target) => d.policy,
|
||||
show: (d) => d.type === "file",
|
||||
},
|
||||
{
|
||||
label: t("fileManager.storagePolicy"),
|
||||
value: (d, target) =>
|
||||
d.policy === ""
|
||||
? t("fileManager.inheritedFromParent")
|
||||
: d.policy,
|
||||
show: (d) => d.type === "dir",
|
||||
},
|
||||
{
|
||||
label: t("fileManager.childFolders"),
|
||||
value: (d, target) =>
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ function LoginForm() {
|
|||
(state) => state.siteConfig.registerEnabled
|
||||
);
|
||||
const title = useSelector((state) => state.siteConfig.title);
|
||||
const QQLogin = useSelector((state) => state.siteConfig.QQLogin);
|
||||
const authn = useSelector((state) => state.siteConfig.authn);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
|
@ -254,6 +255,18 @@ function LoginForm() {
|
|||
});
|
||||
};
|
||||
|
||||
const initQQLogin = () => {
|
||||
setLoading(true);
|
||||
API.post("/user/qq")
|
||||
.then((response) => {
|
||||
window.location.href = response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
ToggleSnackbar("top", "right", error.message, "warning");
|
||||
});
|
||||
};
|
||||
|
||||
const twoFALogin = (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
|
@ -329,20 +342,46 @@ function LoginForm() {
|
|||
/>
|
||||
</FormControl>
|
||||
{loginCaptcha && <CaptchaRender />}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={
|
||||
loading ||
|
||||
(loginCaptcha ? captchaLoading : false)
|
||||
}
|
||||
className={classes.submit}
|
||||
>
|
||||
{t("login.signIn")}
|
||||
</Button>
|
||||
{QQLogin && (
|
||||
<div className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
className={classes.submit}
|
||||
>
|
||||
{t("login.signIn")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
style={{ marginLeft: "10px" }}
|
||||
disabled={loading}
|
||||
className={classes.submit}
|
||||
onClick={initQQLogin}
|
||||
>
|
||||
{t("vas.loginWithQQ")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!QQLogin && (
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={
|
||||
loading ||
|
||||
(loginCaptcha
|
||||
? captchaLoading
|
||||
: false)
|
||||
}
|
||||
className={classes.submit}
|
||||
>
|
||||
{t("login.signIn")}
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
{useAuthn && (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Notice from "../Share/NotFound";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import API from "../../middleware/Api";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import {
|
||||
applyThemes,
|
||||
setSessionStatus,
|
||||
toggleSnackbar,
|
||||
} from "../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function useQuery() {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
}
|
||||
|
||||
export default function QQCallback() {
|
||||
const { t } = useTranslation();
|
||||
const query = useQuery();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
const ApplyThemes = useCallback((theme) => dispatch(applyThemes(theme)), [
|
||||
dispatch,
|
||||
]);
|
||||
const SetSessionStatus = useCallback(
|
||||
(status) => dispatch(setSessionStatus(status)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const [msg, setMsg] = useState("");
|
||||
|
||||
const afterLogin = (data) => {
|
||||
Auth.authenticate(data);
|
||||
|
||||
// 设置用户主题色
|
||||
if (data["preferred_theme"] !== "") {
|
||||
ApplyThemes(data["preferred_theme"]);
|
||||
}
|
||||
|
||||
// 设置登录状态
|
||||
SetSessionStatus(true);
|
||||
|
||||
history.push("/home");
|
||||
ToggleSnackbar("top", "right", t("login.success"), "success");
|
||||
|
||||
localStorage.removeItem("siteConfigCache");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (query.get("error_description")) {
|
||||
setMsg(query.get("error_description"));
|
||||
return;
|
||||
}
|
||||
if (query.get("code") === null) {
|
||||
return;
|
||||
}
|
||||
API.post("/callback/qq", {
|
||||
code: query.get("code"),
|
||||
state: query.get("state"),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.rawData.code === 203) {
|
||||
afterLogin(response.data);
|
||||
} else {
|
||||
history.push(response.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setMsg(error.message);
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [location]);
|
||||
|
||||
return <>{msg !== "" && <Notice msg={msg} />}</>;
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
import React, { useState, useCallback } from "react";
|
||||
import { makeStyles } from "@material-ui/core";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
CircularProgress,
|
||||
} from "@material-ui/core";
|
||||
import { useDispatch } from "react-redux";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import { useCaptcha } from "../../hooks/useCaptcha";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
smsCode: {
|
||||
flexDirection: "row",
|
||||
alignItems: "baseline",
|
||||
},
|
||||
sendButton: {
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
export default function BindPhone(props) {
|
||||
const [phone, setPhone] = useState(props.phone);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [verifyCode, setVerifyCode] = useState("");
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
let countdownTimer, countdownSecond;
|
||||
|
||||
const {
|
||||
captchaLoading,
|
||||
isValidate,
|
||||
validate,
|
||||
CaptchaRender,
|
||||
captchaRefreshRef,
|
||||
captchaParamsRef,
|
||||
} = useCaptcha();
|
||||
|
||||
const savePhoneInfo = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const sendSMS = () => {
|
||||
setCountdown(60);
|
||||
countdownSecond = 60;
|
||||
countdownTimer = setInterval(function () {
|
||||
countdownSecond = countdownSecond - 1;
|
||||
setCountdown(countdownSecond);
|
||||
if (countdownSecond <= 0) {
|
||||
clearInterval(countdownTimer);
|
||||
}
|
||||
}, 1000);
|
||||
return false;
|
||||
};
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
maxWidth={"xs"}
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">绑定手机</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<FormControl fullWidth>
|
||||
<TextField
|
||||
label={"手机号"}
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
/>
|
||||
<CaptchaRender />
|
||||
</FormControl>
|
||||
<FormControl fullWidth className={classes.smsCode}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={"短信验证码"}
|
||||
value={verifyCode}
|
||||
onChange={(e) => setVerifyCode(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
disabled={countdown > 0 || loading}
|
||||
fullWidth
|
||||
onClick={sendSMS}
|
||||
variant="contained"
|
||||
className={classes.sendButton}
|
||||
color="primary"
|
||||
>
|
||||
{countdown <= 0
|
||||
? "发送短信验证码"
|
||||
: `重新发送 (${countdown})`}
|
||||
</Button>
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose}>取消</Button>
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
onClick={() => savePhoneInfo()}
|
||||
>
|
||||
确定
|
||||
{loading && (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
className={classes.buttonProgress}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -12,8 +12,7 @@ import {
|
|||
makeStyles,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import API from "../../middleware/Api";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
|
|
@ -22,6 +21,7 @@ import ListItemIcon from "@material-ui/core/ListItemIcon";
|
|||
import LockIcon from "@material-ui/icons/Lock";
|
||||
import TimerIcon from "@material-ui/icons/Timer";
|
||||
import CasinoIcon from "@material-ui/icons/Casino";
|
||||
import AccountBalanceWalletIcon from "@material-ui/icons/AccountBalanceWallet";
|
||||
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import MuiExpansionPanel from "@material-ui/core/ExpansionPanel";
|
||||
|
|
@ -38,6 +38,7 @@ import Tooltip from "@material-ui/core/Tooltip";
|
|||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import ToggleIcon from "material-ui-toggle-icon";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
|
|
@ -119,7 +120,10 @@ export default function CreatShare(props) {
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
const scoreEnabled = useSelector((state) => state.siteConfig.score_enabled);
|
||||
const scoreRate = useSelector((state) => state.siteConfig.share_score_rate);
|
||||
const lastSubmit = useRef(null);
|
||||
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const [shareURL, setShareURL] = React.useState("");
|
||||
const [values, setValues] = React.useState({
|
||||
|
|
@ -127,10 +131,12 @@ export default function CreatShare(props) {
|
|||
downloads: 1,
|
||||
expires: 24 * 3600,
|
||||
showPassword: false,
|
||||
score: 0,
|
||||
});
|
||||
const [shareOption, setShareOption] = React.useState({
|
||||
password: false,
|
||||
expire: false,
|
||||
score: false,
|
||||
preview: true,
|
||||
});
|
||||
const [customExpires, setCustomExpires] = React.useState(3600);
|
||||
|
|
@ -146,6 +152,15 @@ export default function CreatShare(props) {
|
|||
}
|
||||
}
|
||||
|
||||
// 输入积分
|
||||
if (prop === "score") {
|
||||
if (event.target.value == "0") {
|
||||
setShareOption({ ...shareOption, score: false });
|
||||
} else {
|
||||
setShareOption({ ...shareOption, score: true });
|
||||
}
|
||||
}
|
||||
|
||||
setValues({ ...values, [prop]: event.target.value });
|
||||
};
|
||||
|
||||
|
|
@ -180,6 +195,12 @@ export default function CreatShare(props) {
|
|||
password: "",
|
||||
});
|
||||
}
|
||||
if (prop === "score" && shareOption[prop]) {
|
||||
setValues({
|
||||
...values,
|
||||
score: 0,
|
||||
});
|
||||
}
|
||||
setShareOption({ ...shareOption, [prop]: !shareOption[prop] });
|
||||
};
|
||||
|
||||
|
|
@ -224,6 +245,7 @@ export default function CreatShare(props) {
|
|||
values.expires === -1
|
||||
? parseInt(customExpires)
|
||||
: values.expires,
|
||||
score: parseInt(values.score),
|
||||
preview: shareOption.preview,
|
||||
};
|
||||
lastSubmit.current = submitFormBody;
|
||||
|
|
@ -236,10 +258,12 @@ export default function CreatShare(props) {
|
|||
downloads: 1,
|
||||
expires: 24 * 3600,
|
||||
showPassword: false,
|
||||
score: 0,
|
||||
});
|
||||
setShareOption({
|
||||
password: false,
|
||||
expire: false,
|
||||
score: false,
|
||||
});
|
||||
props.setModalsLoading(false);
|
||||
})
|
||||
|
|
@ -488,6 +512,68 @@ export default function CreatShare(props) {
|
|||
</Typography>
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
{scoreEnabled && (
|
||||
<ExpansionPanel
|
||||
expanded={expanded === "score"}
|
||||
onChange={handleExpand("score")}
|
||||
>
|
||||
<ExpansionPanelSummary
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<AccountBalanceWalletIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t("vas.payToDownload")}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<Checkbox
|
||||
checked={shareOption.score}
|
||||
onChange={handleCheck("score")}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
</ExpansionPanelSummary>
|
||||
|
||||
<ExpansionPanelDetails
|
||||
className={classes.noFlex}
|
||||
>
|
||||
<FormControl
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
fullWidth
|
||||
>
|
||||
<InputLabel htmlFor="filled-adornment-password">
|
||||
{t("vas.creditToBePaid")}
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
id="outlined-adornment-password"
|
||||
type="number"
|
||||
inputProps={{ min: 0 }}
|
||||
value={values.score}
|
||||
onChange={handleChange("score")}
|
||||
labelWidth={180}
|
||||
/>
|
||||
</FormControl>
|
||||
{values.score !== 0 && scoreRate !== "100" && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
className={classes.scoreCalc}
|
||||
>
|
||||
{t("vas.creditGainPredict", {
|
||||
num: Math.ceil(
|
||||
(values.score * scoreRate) /
|
||||
100
|
||||
),
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
)}
|
||||
<ExpansionPanel
|
||||
expanded={expanded === "preview"}
|
||||
onChange={handleExpand("preview")}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Dialog, makeStyles } from "@material-ui/core";
|
||||
import API from "../../middleware/Api";
|
||||
import { useDispatch } from "react-redux";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import { FolderOpenOutlined, Storage } from "@material-ui/icons";
|
||||
import PathSelector from "../FileManager/PathSelector";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
formGroup: {
|
||||
display: "flex",
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
formIcon: {
|
||||
marginTop: 21,
|
||||
marginRight: 19,
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
input: {
|
||||
width: 250,
|
||||
},
|
||||
dialogContent: {
|
||||
paddingTop: 24,
|
||||
paddingRight: 24,
|
||||
paddingBottom: 8,
|
||||
paddingLeft: 24,
|
||||
},
|
||||
button: {
|
||||
marginTop: 8,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function CreateWebDAVMount(props) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState({
|
||||
policy: "",
|
||||
path: "/",
|
||||
});
|
||||
const [policies, setPolicies] = useState([]);
|
||||
const [pathSelectDialog, setPathSelectDialog] = React.useState(false);
|
||||
const [selectedPath, setSelectedPath] = useState("");
|
||||
// eslint-disable-next-line
|
||||
const [selectedPathName, setSelectedPathName] = useState("");
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setMoveTarget = (folder) => {
|
||||
const path =
|
||||
folder.path === "/"
|
||||
? folder.path + folder.name
|
||||
: folder.path + "/" + folder.name;
|
||||
setSelectedPath(path);
|
||||
setSelectedPathName(folder.name);
|
||||
};
|
||||
|
||||
const handleInputChange = (name) => (e) => {
|
||||
setValue({
|
||||
...value,
|
||||
[name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const selectPath = () => {
|
||||
setValue({
|
||||
...value,
|
||||
path: selectedPath === "//" ? "/" : selectedPath,
|
||||
});
|
||||
setPathSelectDialog(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
API.get("/user/setting/policies")
|
||||
.then((response) => {
|
||||
setPolicies(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<Dialog
|
||||
open={pathSelectDialog}
|
||||
onClose={() => setPathSelectDialog(false)}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{t("navbar.addTagDialog.selectFolder")}
|
||||
</DialogTitle>
|
||||
<PathSelector
|
||||
presentPath="/"
|
||||
selected={[]}
|
||||
onSelect={setMoveTarget}
|
||||
/>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setPathSelectDialog(false)}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={selectPath}
|
||||
color="primary"
|
||||
disabled={selectedPath === ""}
|
||||
>
|
||||
{t("ok", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<div className={classes.dialogContent}>
|
||||
<div className={classes.formContainer}>
|
||||
<div className={classes.formGroup}>
|
||||
<div className={classes.formIcon}>
|
||||
<Storage />
|
||||
</div>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-label">
|
||||
{t("fileManager.storagePolicy")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
className={classes.input}
|
||||
labelId="demo-simple-select-label"
|
||||
value={value.policy}
|
||||
onChange={handleInputChange("policy")}
|
||||
>
|
||||
{policies.map((v, k) => (
|
||||
<MenuItem key={k} value={v.id}>
|
||||
{v.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={classes.formGroup}>
|
||||
<div className={classes.formIcon}>
|
||||
<FolderOpenOutlined />
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
value={value.path}
|
||||
onChange={handleInputChange("path")}
|
||||
className={classes.input}
|
||||
label={t("setting.rootFolder")}
|
||||
/>
|
||||
<br />
|
||||
<Button
|
||||
className={classes.button}
|
||||
color="primary"
|
||||
onClick={() => setPathSelectDialog(true)}
|
||||
>
|
||||
{t("navbar.addTagDialog.selectFolder")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={value.path === "" || value.policy === ""}
|
||||
color="primary"
|
||||
onClick={() => props.callback(value)}
|
||||
>
|
||||
{t("ok", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import {
|
|||
Checkbox,
|
||||
} from "@material-ui/core";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useInterval, usePrevious, useGetState } from "ahooks";
|
||||
import { cancelDirectoryDownload } from "../../redux/explorer/action";
|
||||
import Auth from "../../middleware/Auth";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import React from "react";
|
||||
import { Dialog } from "@material-ui/core";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function PurchaseShareDialog() {
|
||||
const { t } = useTranslation();
|
||||
const purchase = useSelector((state) => state.explorer.purchase);
|
||||
return (
|
||||
<>
|
||||
{purchase && (
|
||||
<Dialog
|
||||
open={purchase}
|
||||
onClose={purchase.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{t("vas.sharePurchaseTitle", { score: purchase.score })}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{t("vas.sharePurchaseDescription")}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={purchase.onClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => purchase.callback()}
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
{t("ok", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
makeStyles,
|
||||
} from "@material-ui/core";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import API from "../../middleware/Api";
|
||||
import Select from "@material-ui/core/Select";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import { setModalsLoading, toggleSnackbar } from "../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
wrapper: {
|
||||
margin: theme.spacing(1),
|
||||
position: "relative",
|
||||
},
|
||||
buttonProgress: {
|
||||
color: theme.palette.secondary.light,
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
marginTop: -12,
|
||||
marginLeft: -12,
|
||||
},
|
||||
input: {
|
||||
width: 250,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function RelocateDialog(props) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedPolicy, setSelectedPolicy] = useState("");
|
||||
const [policies, setPolicies] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
const policy = useSelector((state) => state.explorer.currentPolicy);
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const SetModalsLoading = useCallback(
|
||||
(status) => {
|
||||
dispatch(setModalsLoading(status));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const submitRelocate = (e) => {
|
||||
if (e != null) {
|
||||
e.preventDefault();
|
||||
}
|
||||
SetModalsLoading(true);
|
||||
|
||||
const dirs = [],
|
||||
items = [];
|
||||
// eslint-disable-next-line
|
||||
props.selected.map((value) => {
|
||||
if (value.type === "dir") {
|
||||
dirs.push(value.id);
|
||||
} else {
|
||||
items.push(value.id);
|
||||
}
|
||||
});
|
||||
|
||||
API.post("/file/relocate", {
|
||||
src: {
|
||||
dirs: dirs,
|
||||
items: items,
|
||||
},
|
||||
dst_policy_id: selectedPolicy,
|
||||
})
|
||||
.then(() => {
|
||||
props.onClose();
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
t("modals.taskCreated"),
|
||||
"success"
|
||||
);
|
||||
SetModalsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
SetModalsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.open) {
|
||||
API.get("/user/setting/policies")
|
||||
.then((response) => {
|
||||
setPolicies(response.data);
|
||||
setSelectedPolicy(policy.id);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [props.open]);
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{t("vas.migrateStoragePolicy")}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent className={classes.contentFix}>
|
||||
<Select
|
||||
className={classes.input}
|
||||
labelId="demo-simple-select-label"
|
||||
value={selectedPolicy}
|
||||
onChange={(e) => setSelectedPolicy(e.target.value)}
|
||||
>
|
||||
{policies.map((v, k) => (
|
||||
<MenuItem key={k} value={v.id}>
|
||||
{v.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
onClick={submitRelocate}
|
||||
color="primary"
|
||||
disabled={selectedPolicy === "" || props.modalsLoading}
|
||||
>
|
||||
{t("ok", { ns: "common" })}
|
||||
{props.modalsLoading && (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
className={classes.buttonProgress}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,25 +7,25 @@ import {
|
|||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
makeStyles, TextField
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import PathSelector from "../FileManager/PathSelector";
|
||||
import { useDispatch } from "react-redux";
|
||||
import API, { AppError } from "../../middleware/Api";
|
||||
import {
|
||||
refreshFileList,
|
||||
setModalsLoading,
|
||||
toggleSnackbar,
|
||||
} from "../../redux/explorer";
|
||||
import { setModalsLoading, toggleSnackbar } from "../../redux/explorer";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { FolderOpenOutlined } from "@material-ui/icons";
|
||||
import { DnsOutlined, FolderOpenOutlined } from "@material-ui/icons";
|
||||
import { pathBack } from "../../utils";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import { AccountCircle } from "mdi-material-ui";
|
||||
import { useTheme } from "@material-ui/core/styles";
|
||||
import useMediaQuery from "@material-ui/core/useMediaQuery";
|
||||
import LinkIcon from "@material-ui/icons/Link";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import FormControl from "@material-ui/core/FormControl";
|
||||
import InputLabel from "@material-ui/core/InputLabel";
|
||||
import Select from "@material-ui/core/Select";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
contentFix: {
|
||||
|
|
@ -49,24 +49,22 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
forumInput: {
|
||||
flexGrow: 1,
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
export default function RemoteDownload(props) {
|
||||
const { t } = useTranslation();
|
||||
const [selectPathOpen,setSelectPathOpen] = useState(false);
|
||||
const [selectPathOpen, setSelectPathOpen] = useState(false);
|
||||
const [selectedPath, setSelectedPath] = useState("");
|
||||
const [selectedPathName, setSelectedPathName] = useState("");
|
||||
const [downloadTo, setDownloadTo] = useState("");
|
||||
const [url, setUrl] = useState("");
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [nodesLoading, setNodesLoading] = useState(false);
|
||||
const [preferredNode, setPreferredNode] = useState(0);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
useEffect(()=>{
|
||||
if (props.open){
|
||||
setDownloadTo(props.presentPath)
|
||||
}
|
||||
},[props.open])
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const user = Auth.GetUser();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
|
|
@ -74,12 +72,25 @@ export default function RemoteDownload(props) {
|
|||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
const SetModalsLoading = useCallback(
|
||||
(status) => {
|
||||
dispatch(setModalsLoading(status));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.open) {
|
||||
setDownloadTo(props.presentPath);
|
||||
|
||||
if (user.group.selectNode && nodes.length === 0) {
|
||||
setNodesLoading(true);
|
||||
API.get("/user/setting/nodes")
|
||||
.then((response) => {
|
||||
setNodes(response.data);
|
||||
setNodesLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setNodes([]);
|
||||
setNodesLoading(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [props.open]);
|
||||
|
||||
const setDownloadToPath = (folder) => {
|
||||
const path =
|
||||
|
|
@ -99,10 +110,8 @@ export default function RemoteDownload(props) {
|
|||
e.preventDefault();
|
||||
props.setModalsLoading(true);
|
||||
API.post("/aria2/torrent/" + props.torrent.id, {
|
||||
dst:
|
||||
downloadTo === "//"
|
||||
? "/"
|
||||
: downloadTo,
|
||||
dst: downloadTo === "//" ? "/" : downloadTo,
|
||||
preferred_node: preferredNode,
|
||||
})
|
||||
.then(() => {
|
||||
ToggleSnackbar(
|
||||
|
|
@ -115,12 +124,7 @@ export default function RemoteDownload(props) {
|
|||
props.setModalsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
ToggleSnackbar("top", "right", error.message, "warning");
|
||||
props.setModalsLoading(false);
|
||||
});
|
||||
};
|
||||
|
|
@ -130,10 +134,8 @@ export default function RemoteDownload(props) {
|
|||
props.setModalsLoading(true);
|
||||
API.post("/aria2/url", {
|
||||
url: url.split("\n"),
|
||||
dst:
|
||||
downloadTo === "//"
|
||||
? "/"
|
||||
: downloadTo,
|
||||
dst: downloadTo === "//" ? "/" : downloadTo,
|
||||
preferred_node: preferredNode,
|
||||
})
|
||||
.then((response) => {
|
||||
const failed = response.data
|
||||
|
|
@ -162,12 +164,7 @@ export default function RemoteDownload(props) {
|
|||
props.setModalsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
props.setModalsLoading(false);
|
||||
});
|
||||
};
|
||||
|
|
@ -176,96 +173,167 @@ export default function RemoteDownload(props) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{t("modals.newRemoteDownloadTitle")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
<div className={classes.formGroup}>
|
||||
<div className={classes.forumInput}>
|
||||
<TextField
|
||||
variant={"outlined"}
|
||||
label={t("modals.remoteDownloadURL")}
|
||||
autoFocus
|
||||
fullWidth
|
||||
disabled={props.torrent}
|
||||
multiline
|
||||
value={props.torrent?props.torrent.name:url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder={t(
|
||||
"modals.remoteDownloadURLDescription"
|
||||
)}
|
||||
InputProps={{
|
||||
startAdornment: !isMobile&&(
|
||||
<InputAdornment position="start">
|
||||
<LinkIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
|
||||
}}
|
||||
/>
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{t("modals.newRemoteDownloadTitle")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
<div className={classes.formGroup}>
|
||||
<div className={classes.forumInput}>
|
||||
<TextField
|
||||
variant={"outlined"}
|
||||
label={t("modals.remoteDownloadURL")}
|
||||
autoFocus
|
||||
fullWidth
|
||||
disabled={props.torrent}
|
||||
multiline
|
||||
value={
|
||||
props.torrent ? props.torrent.name : url
|
||||
}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder={t(
|
||||
"modals.remoteDownloadURLDescription"
|
||||
)}
|
||||
InputProps={{
|
||||
startAdornment: !isMobile && (
|
||||
<InputAdornment position="start">
|
||||
<LinkIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.formGroup}>
|
||||
<div className={classes.forumInput}>
|
||||
<TextField
|
||||
variant={"outlined"}
|
||||
fullWidth
|
||||
value={downloadTo}
|
||||
onChange={(e) => setDownloadTo(e.target.value)}
|
||||
className={classes.input}
|
||||
label={t("modals.remoteDownloadDst")}
|
||||
InputProps={{
|
||||
startAdornment: !isMobile&&(
|
||||
<InputAdornment position="start">
|
||||
<FolderOpenOutlined />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment:(
|
||||
<InputAdornment position="end">
|
||||
<Button
|
||||
className={classes.button}
|
||||
color="primary"
|
||||
onClick={() => setSelectPathOpen(true)}
|
||||
>
|
||||
{t("navbar.addTagDialog.selectFolder")}
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<div className={classes.formGroup}>
|
||||
<div className={classes.forumInput}>
|
||||
<TextField
|
||||
variant={"outlined"}
|
||||
fullWidth
|
||||
value={downloadTo}
|
||||
onChange={(e) =>
|
||||
setDownloadTo(e.target.value)
|
||||
}
|
||||
className={classes.input}
|
||||
label={t("modals.remoteDownloadDst")}
|
||||
InputProps={{
|
||||
startAdornment: !isMobile && (
|
||||
<InputAdornment position="start">
|
||||
<FolderOpenOutlined />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<Button
|
||||
className={classes.button}
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
setSelectPathOpen(true)
|
||||
}
|
||||
>
|
||||
{t(
|
||||
"navbar.addTagDialog.selectFolder"
|
||||
)}
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
onClick={props.torrent?submitTorrentDownload:submitDownload}
|
||||
color="primary"
|
||||
disabled={(url === "" && props.torrent===null) || downloadTo==="" || props.modalsLoading}
|
||||
>
|
||||
{t("modals.createTask")}
|
||||
{props.modalsLoading && (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
className={classes.buttonProgress}
|
||||
/>
|
||||
{user.group.selectNode && (
|
||||
<div className={classes.formGroup}>
|
||||
<div className={classes.forumInput}>
|
||||
<FormControl
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
className={classes.formControl}
|
||||
>
|
||||
<InputLabel id="demo-simple-select-label">
|
||||
{t("modals.remoteDownloadNode")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
startAdornment={
|
||||
!isMobile && (
|
||||
<InputAdornment position="start">
|
||||
{nodesLoading ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
/>
|
||||
) : (
|
||||
<DnsOutlined />
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
label={t(
|
||||
"modals.remoteDownloadNode"
|
||||
)}
|
||||
value={preferredNode}
|
||||
onChange={(e) =>
|
||||
setPreferredNode(e.target.value)
|
||||
}
|
||||
>
|
||||
<MenuItem value={0}>
|
||||
<em>
|
||||
{t(
|
||||
"modals.remoteDownloadNodeAuto"
|
||||
)}
|
||||
</em>
|
||||
</MenuItem>
|
||||
{nodes.map((node) => (
|
||||
<MenuItem
|
||||
key={node.id}
|
||||
value={node.id}
|
||||
>
|
||||
{node.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<div className={classes.wrapper}>
|
||||
<Button
|
||||
onClick={
|
||||
props.torrent
|
||||
? submitTorrentDownload
|
||||
: submitDownload
|
||||
}
|
||||
color="primary"
|
||||
disabled={
|
||||
(url === "" && props.torrent === null) ||
|
||||
downloadTo === "" ||
|
||||
props.modalsLoading
|
||||
}
|
||||
>
|
||||
{t("modals.createTask")}
|
||||
{props.modalsLoading && (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
className={classes.buttonProgress}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={selectPathOpen}
|
||||
|
|
@ -307,6 +375,6 @@ export default function RemoteDownload(props) {
|
|||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
makeStyles,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import API from "../../middleware/Api";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import { reportReasons } from "../../config";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
widthAnimation: {},
|
||||
shareUrl: {
|
||||
minWidth: "400px",
|
||||
},
|
||||
wrapper: {
|
||||
margin: theme.spacing(1),
|
||||
position: "relative",
|
||||
},
|
||||
buttonProgress: {
|
||||
color: theme.palette.secondary.light,
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
},
|
||||
flexCenter: {
|
||||
alignItems: "center",
|
||||
},
|
||||
noFlex: {
|
||||
display: "block",
|
||||
},
|
||||
scoreCalc: {
|
||||
marginTop: 10,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function Report(props) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const classes = useStyles();
|
||||
const [reason, setReason] = useState("0");
|
||||
const [des, setDes] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
const reportEnabled = useSelector(
|
||||
(state) => state.siteConfig.report_enabled
|
||||
);
|
||||
|
||||
const onClose = () => {
|
||||
props.onClose();
|
||||
setTimeout(() => {
|
||||
setDes("");
|
||||
setReason("0");
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const submitReport = () => {
|
||||
setLoading(true);
|
||||
API.post("/share/report/" + props.share.key, {
|
||||
des: des,
|
||||
reason: parseInt(reason),
|
||||
})
|
||||
.then(() => {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
t("vas.reportSuccessful"),
|
||||
"success"
|
||||
);
|
||||
setLoading(false);
|
||||
onClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={onClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
className={classes.widthAnimation}
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">{t("vas.report")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControl component="fieldset">
|
||||
<RadioGroup
|
||||
aria-label="gender"
|
||||
name="gender1"
|
||||
value={reason}
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
>
|
||||
{reportReasons.map((v, k) => (
|
||||
<FormControlLabel
|
||||
key={k}
|
||||
value={k.toString()}
|
||||
control={<Radio />}
|
||||
label={t(v)}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="standard-multiline-static"
|
||||
label={t("vas.additionalDescription")}
|
||||
multiline
|
||||
value={des}
|
||||
onChange={(e) => setDes(e.target.value)}
|
||||
variant="filled"
|
||||
rows={4}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={submitReport}
|
||||
color="secondary"
|
||||
disabled={loading || !reportEnabled}
|
||||
>
|
||||
{t("ok", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
makeStyles,
|
||||
} from "@material-ui/core";
|
||||
import { useSelector } from "react-redux";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
widthAnimation: {},
|
||||
content: {
|
||||
overflowWrap: "break-word",
|
||||
},
|
||||
}));
|
||||
|
||||
export default function SiteNotice() {
|
||||
const { t } = useTranslation();
|
||||
const content = useSelector((state) => state.siteConfig.site_notice);
|
||||
const classes = useStyles();
|
||||
const [show, setShow] = useState(false);
|
||||
const setRead = () => {
|
||||
setShow(false);
|
||||
Auth.SetPreference("notice_read", content);
|
||||
};
|
||||
useEffect(() => {
|
||||
const newNotice = Auth.GetPreference("notice_read");
|
||||
if (content !== "" && newNotice !== content) {
|
||||
setShow(true);
|
||||
}
|
||||
}, [content]);
|
||||
return (
|
||||
<Dialog
|
||||
open={show}
|
||||
onClose={() => setShow(false)}
|
||||
aria-labelledby="form-dialog-title"
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{t("vas.announcement")}
|
||||
</DialogTitle>
|
||||
<DialogContent
|
||||
className={classes.content}
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setRead()} color="primary">
|
||||
{t("vas.dontShowAgain")}
|
||||
</Button>
|
||||
<Button onClick={() => setShow(false)}>
|
||||
{t("close", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { connect } from "react-redux";
|
|||
import ShareIcon from "@material-ui/icons/Share";
|
||||
import MusicNote from "@material-ui/icons/MusicNote";
|
||||
import BackIcon from "@material-ui/icons/ArrowBack";
|
||||
import SdStorage from "@material-ui/icons/SdStorage";
|
||||
import OpenIcon from "@material-ui/icons/OpenInNew";
|
||||
import DownloadIcon from "@material-ui/icons/CloudDownload";
|
||||
import RenameIcon from "@material-ui/icons/BorderColor";
|
||||
|
|
@ -72,6 +73,7 @@ import {
|
|||
startDirectoryDownload,
|
||||
startDownload,
|
||||
} from "../../redux/explorer/action";
|
||||
import PolicySwitcher from "./PolicySwitcher";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import MuiListItem from "@material-ui/core/ListItem";
|
||||
|
||||
|
|
@ -323,14 +325,14 @@ class NavbarCompoment extends Component {
|
|||
this.unlisten();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount = () => {
|
||||
changeThemeColor(
|
||||
this.props.selected.length <= 1 &&
|
||||
!(!this.props.isMultiple && this.props.withFile)
|
||||
? this.props.theme.palette.primary.main
|
||||
: this.props.theme.palette.background.default
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps = (nextProps) => {
|
||||
if (
|
||||
|
|
@ -440,24 +442,34 @@ class NavbarCompoment extends Component {
|
|||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
{user.group.webdav && (
|
||||
<ListItem
|
||||
button
|
||||
key="WebDAV"
|
||||
onClick={() =>
|
||||
this.props.history.push("/webdav?")
|
||||
}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Devices
|
||||
className={classes.iconFix}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t("navbar.connect")}
|
||||
<ListItem
|
||||
button
|
||||
key="容量配额"
|
||||
onClick={() =>
|
||||
this.props.history.push("/quota?")
|
||||
}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<SdStorage
|
||||
className={classes.iconFix}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("vas.quota")} />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
key="WebDAV"
|
||||
onClick={() =>
|
||||
this.props.history.push("/connect?")
|
||||
}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Devices className={classes.iconFix} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t("navbar.connect")}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
button
|
||||
|
|
@ -901,9 +913,13 @@ class NavbarCompoment extends Component {
|
|||
|
||||
{this.props.selected.length === 0 && <UserAvatar />}
|
||||
{this.props.selected.length === 0 &&
|
||||
pathHelper.isMobile() &&
|
||||
(isHomePage || this.props.shareInfo) && (
|
||||
<SubActions inherit />
|
||||
pathHelper.isMobile() && (
|
||||
<>
|
||||
{isHomePage && <PolicySwitcher />}
|
||||
{(isHomePage || this.props.shareInfo) && (
|
||||
<SubActions inherit />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
import React, { useCallback } from "react";
|
||||
import {
|
||||
Avatar,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
} from "@material-ui/core";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import { Nas } from "mdi-material-ui";
|
||||
import Popover from "@material-ui/core/Popover";
|
||||
import API from "../../middleware/Api";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Backup, Check } from "@material-ui/icons";
|
||||
import { blue, green } from "@material-ui/core/colors";
|
||||
import List from "@material-ui/core/List";
|
||||
import { refreshFileList, toggleSnackbar } from "../../redux/explorer";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import Box from "@material-ui/core/Box";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import pathHelper from "../../utils/page";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
uploadFromFile: {
|
||||
backgroundColor: blue[100],
|
||||
color: blue[600],
|
||||
},
|
||||
policySelected: {
|
||||
backgroundColor: green[100],
|
||||
color: green[800],
|
||||
},
|
||||
header: {
|
||||
padding: "8px 16px",
|
||||
fontSize: 14,
|
||||
},
|
||||
list: {
|
||||
minWidth: 300,
|
||||
maxHeight: 600,
|
||||
overflow: "auto",
|
||||
},
|
||||
}));
|
||||
|
||||
const PolicySwitcher = () => {
|
||||
const { t } = useTranslation();
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const [policies, setPolicies] = React.useState([]);
|
||||
const [loading, setLoading] = React.useState(null);
|
||||
const policy = useSelector((state) => state.explorer.currentPolicy);
|
||||
const path = useSelector((state) => state.navigator.path);
|
||||
const dispatch = useDispatch();
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
const RefreshFileList = useCallback(() => dispatch(refreshFileList()), [
|
||||
dispatch,
|
||||
]);
|
||||
const search = useSelector((state) => state.explorer.search);
|
||||
|
||||
const handleClick = (event) => {
|
||||
if (policies.length === 0) {
|
||||
API.get("/user/setting/policies", {})
|
||||
.then((response) => {
|
||||
setPolicies(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
}
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const switchTo = (id) => {
|
||||
if (id === policy.id) {
|
||||
handleClose();
|
||||
return;
|
||||
}
|
||||
setLoading(id);
|
||||
API.post("/webdav/mount", {
|
||||
path: path,
|
||||
policy: id,
|
||||
})
|
||||
.then(() => {
|
||||
ToggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
t("vas.folderPolicySwitched"),
|
||||
"success"
|
||||
);
|
||||
RefreshFileList();
|
||||
setLoading(null);
|
||||
handleClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
setLoading(null);
|
||||
handleClose();
|
||||
});
|
||||
};
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? "simple-popover" : undefined;
|
||||
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<>
|
||||
{pathHelper.isHomePage(location.pathname) && !search && (
|
||||
<Tooltip title={t("vas.switchFolderPolicy")} placement="bottom">
|
||||
<IconButton onClick={handleClick} color="inherit">
|
||||
<Nas />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
>
|
||||
<div className={classes.header}>
|
||||
<Box color={"text.secondary"}>
|
||||
{t("vas.setPolicyForFolder")}
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<List className={classes.list}>
|
||||
{policies.map((value, index) => (
|
||||
<ListItem
|
||||
button
|
||||
component="label"
|
||||
key={index}
|
||||
onClick={() => switchTo(value.id)}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
{value.id === loading && (
|
||||
<CircularProgress
|
||||
size={35}
|
||||
color="secondary"
|
||||
/>
|
||||
)}
|
||||
{value.id !== loading && (
|
||||
<>
|
||||
{value.id === policy.id && (
|
||||
<Avatar
|
||||
className={
|
||||
classes.policySelected
|
||||
}
|
||||
>
|
||||
<Check />
|
||||
</Avatar>
|
||||
)}
|
||||
{value.id !== policy.id && (
|
||||
<Avatar
|
||||
className={
|
||||
classes.uploadFromFile
|
||||
}
|
||||
>
|
||||
<Backup />
|
||||
</Avatar>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={value.name} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Divider />
|
||||
<div className={classes.header}>
|
||||
<Link
|
||||
onClick={() => handleClose()}
|
||||
component={RouterLink}
|
||||
to={"/connect?tab=1"}
|
||||
color={"secondary"}
|
||||
>
|
||||
{t("vas.manageMount")}
|
||||
</Link>
|
||||
</div>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PolicySwitcher;
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import { withRouter } from "react-router";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
|
@ -110,7 +111,7 @@ class StorageBarCompoment extends Component {
|
|||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
this.props.t("navbar.exceedQuota"),
|
||||
this.props.t("vas.exceedQuota"),
|
||||
"warning"
|
||||
);
|
||||
} else {
|
||||
|
|
@ -135,13 +136,23 @@ class StorageBarCompoment extends Component {
|
|||
className={classes.stickFooter}
|
||||
>
|
||||
<Divider />
|
||||
<ButtonBase>
|
||||
<ButtonBase onClick={() => this.props.history.push("/quota")}>
|
||||
<div className={classes.storageContainer}>
|
||||
<StorageIcon className={classes.iconFix} />
|
||||
<div className={classes.detail}>
|
||||
<Typography variant={"subtitle2"}>
|
||||
{t("navbar.storage")}
|
||||
{t("navbar.storage") + " "}
|
||||
{this.state.showExpand && (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
color={"secondary"}
|
||||
to={"/buy"}
|
||||
>
|
||||
{t("vas.extendStorage")}
|
||||
</Link>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
<LinearProgress
|
||||
className={classes.bar}
|
||||
color="secondary"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { withRouter } from "react-router-dom";
|
||||
import pathHelper from "../../utils/page";
|
||||
import DarkModeSwitcher from "./DarkModeSwitcher";
|
||||
import PolicySwitcher from "./PolicySwitcher";
|
||||
import { Home } from "@material-ui/icons";
|
||||
import { setUserPopover } from "../../redux/explorer";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
|
@ -113,6 +114,7 @@ class UserAvatarCompoment extends Component {
|
|||
<DarkModeSwitcher position="top" />
|
||||
{loginCheck && (
|
||||
<>
|
||||
<PolicySwitcher />
|
||||
<Tooltip
|
||||
title={t("navbar.setting")}
|
||||
placement="bottom"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import DateIcon from "@material-ui/icons/DateRange";
|
|||
import EmailIcon from "@material-ui/icons/Email";
|
||||
import HomeIcon from "@material-ui/icons/Home";
|
||||
import LinkIcon from "@material-ui/icons/Phonelink";
|
||||
import AlarmOff from "@material-ui/icons/AlarmOff";
|
||||
import InputIcon from "@material-ui/icons/Input";
|
||||
import SecurityIcon from "@material-ui/icons/Security";
|
||||
import NickIcon from "@material-ui/icons/PermContactCalendar";
|
||||
|
|
@ -18,34 +19,36 @@ import ToggleButton from "@material-ui/lab/ToggleButton";
|
|||
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
|
||||
import RightIcon from "@material-ui/icons/KeyboardArrowRight";
|
||||
import {
|
||||
ListItemIcon,
|
||||
withStyles,
|
||||
Button,
|
||||
Divider,
|
||||
TextField,
|
||||
Avatar,
|
||||
Paper,
|
||||
Typography,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemIcon,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Paper,
|
||||
Switch,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
import SettingsInputHdmi from "@material-ui/icons/SettingsInputHdmi";
|
||||
import { blue, green, yellow } from "@material-ui/core/colors";
|
||||
import API from "../../middleware/Api";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import { withRouter } from "react-router";
|
||||
import TimeAgo from "timeago-react";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import {
|
||||
Brightness3,
|
||||
GitHub,
|
||||
Home,
|
||||
ConfirmationNumber,
|
||||
ListAlt,
|
||||
PermContactCalendar,
|
||||
Schedule,
|
||||
|
|
@ -54,6 +57,7 @@ import {
|
|||
import Authn from "./Authn";
|
||||
import { formatLocalTime, timeZone } from "../../utils/datetime";
|
||||
import TimeZoneDialog from "../Modals/TimeZone";
|
||||
import BindPhone from "../Modals/BindPhone";
|
||||
import {
|
||||
applyThemes,
|
||||
changeViewMethod,
|
||||
|
|
@ -223,19 +227,13 @@ class UserSettingCompoment extends Component {
|
|||
showWebDavUserName: false,
|
||||
changeWebDavPwd: false,
|
||||
groupBackModal: false,
|
||||
changePolicy: false,
|
||||
changeTimeZone: false,
|
||||
bindPhone: false,
|
||||
settings: {
|
||||
uid: 0,
|
||||
group_expires: 0,
|
||||
policy: {
|
||||
current: {
|
||||
name: "-",
|
||||
id: "",
|
||||
},
|
||||
options: [],
|
||||
},
|
||||
qq: "",
|
||||
phone: "",
|
||||
homepage: true,
|
||||
two_factor: "",
|
||||
two_fa_secret: "",
|
||||
|
|
@ -257,7 +255,7 @@ class UserSettingCompoment extends Component {
|
|||
showWebDavUserName: false,
|
||||
changeWebDavPwd: false,
|
||||
groupBackModal: false,
|
||||
changePolicy: false,
|
||||
bindPhone: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -295,6 +293,27 @@ class UserSettingCompoment extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
doChangeGroup = () => {
|
||||
API.patch("/user/setting/vip", {})
|
||||
.then(() => {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
this.props.t("vas.cancelSubscription"),
|
||||
"success"
|
||||
);
|
||||
this.handleClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
useGravatar = () => {
|
||||
this.setState({
|
||||
loading: "gravatar",
|
||||
|
|
@ -356,6 +375,45 @@ class UserSettingCompoment extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
bindQQ = () => {
|
||||
this.setState({
|
||||
loading: "nick",
|
||||
});
|
||||
API.patch("/user/setting/qq", {})
|
||||
.then((response) => {
|
||||
if (response.data === "") {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
this.props.t("vas.qqUnlinked"),
|
||||
"success"
|
||||
);
|
||||
this.setState({
|
||||
settings: {
|
||||
...this.state.settings,
|
||||
qq: false,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
window.location.href = response.data;
|
||||
}
|
||||
this.handleClose();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
this.setState({
|
||||
loading: "",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
uploadAvatar = () => {
|
||||
this.setState({
|
||||
loading: "avatar",
|
||||
|
|
@ -609,6 +667,10 @@ class UserSettingCompoment extends Component {
|
|||
Auth.SetPreference("theme_mode", newMode);
|
||||
};
|
||||
|
||||
bindPhone = () => {
|
||||
this.setState({ bindPhone: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, t } = this.props;
|
||||
const user = Auth.GetUser();
|
||||
|
|
@ -706,8 +768,36 @@ class UserSettingCompoment extends Component {
|
|||
</Typography>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
{/*<Divider />*/}
|
||||
{/*<ListItem button onClick={() => this.bindPhone()}>*/}
|
||||
{/* <ListItemIcon className={classes.iconFix}>*/}
|
||||
{/* <SettingsInputHdmi />*/}
|
||||
{/* </ListItemIcon>*/}
|
||||
{/* <ListItemText primary="手机号" />*/}
|
||||
|
||||
{/* <ListItemSecondaryAction*/}
|
||||
{/* className={classes.flexContainer}*/}
|
||||
{/* >*/}
|
||||
{/* <Typography*/}
|
||||
{/* className={classes.infoTextWithIcon}*/}
|
||||
{/* color="textSecondary"*/}
|
||||
{/* >*/}
|
||||
{/* {this.state.settings.phone*/}
|
||||
{/* ? this.state.settings.phone*/}
|
||||
{/* : "绑定"}*/}
|
||||
{/* </Typography>*/}
|
||||
{/* <RightIcon*/}
|
||||
{/* className={classes.rightIconWithText}*/}
|
||||
{/* />*/}
|
||||
{/* </ListItemSecondaryAction>*/}
|
||||
{/*</ListItem>*/}
|
||||
<Divider />
|
||||
<ListItem button>
|
||||
<ListItem
|
||||
button
|
||||
onClick={() =>
|
||||
this.props.history.push("/buy?tab=1")
|
||||
}
|
||||
>
|
||||
<ListItemIcon className={classes.iconFix}>
|
||||
<GroupIcon />
|
||||
</ListItemIcon>
|
||||
|
|
@ -719,6 +809,106 @@ class UserSettingCompoment extends Component {
|
|||
color="textSecondary"
|
||||
>
|
||||
{user.group.name}
|
||||
{this.state.settings.group_expires && (
|
||||
<span>
|
||||
<Tooltip
|
||||
key={0}
|
||||
title={formatLocalTime(
|
||||
this.state.settings
|
||||
.group_expires
|
||||
)}
|
||||
>
|
||||
<Trans
|
||||
i18nKey="vas.groupExpire"
|
||||
components={[
|
||||
<TimeAgo
|
||||
key={0}
|
||||
datetime={
|
||||
this.state
|
||||
.settings
|
||||
.group_expires
|
||||
}
|
||||
locale={t(
|
||||
"timeAgoLocaleCode",
|
||||
{
|
||||
ns: "common",
|
||||
}
|
||||
)}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</Typography>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
{this.state.settings.group_expires && (
|
||||
<div>
|
||||
<Divider />
|
||||
<ListItem
|
||||
button
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
groupBackModal: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<ListItemIcon
|
||||
className={classes.iconFix}
|
||||
>
|
||||
<AlarmOff />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t(
|
||||
"vas.manuallyCancelSubscription"
|
||||
)}
|
||||
/>
|
||||
|
||||
<ListItemSecondaryAction>
|
||||
<RightIcon
|
||||
className={classes.rightIcon}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
</div>
|
||||
)}
|
||||
<Divider />
|
||||
<ListItem button onClick={() => this.bindQQ()}>
|
||||
<ListItemIcon className={classes.iconFix}>
|
||||
<SettingsInputHdmi />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("vas.qqAccount")} />
|
||||
|
||||
<ListItemSecondaryAction
|
||||
className={classes.flexContainer}
|
||||
>
|
||||
<Typography
|
||||
className={classes.infoTextWithIcon}
|
||||
color="textSecondary"
|
||||
>
|
||||
{this.state.settings.qq
|
||||
? t("vas.unlink")
|
||||
: t("vas.connect")}
|
||||
</Typography>
|
||||
<RightIcon
|
||||
className={classes.rightIconWithText}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem button>
|
||||
<ListItemIcon className={classes.iconFix}>
|
||||
<ConfirmationNumber />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("vas.credits")} />
|
||||
|
||||
<ListItemSecondaryAction>
|
||||
<Typography
|
||||
className={classes.infoText}
|
||||
color="textSecondary"
|
||||
>
|
||||
{user.score}
|
||||
</Typography>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
|
|
@ -1034,7 +1224,7 @@ class UserSettingCompoment extends Component {
|
|||
<ListItem
|
||||
button
|
||||
onClick={() =>
|
||||
this.props.history.push("/webdav?")
|
||||
this.props.history.push("/connect?")
|
||||
}
|
||||
>
|
||||
<ListItemIcon
|
||||
|
|
@ -1058,61 +1248,13 @@ class UserSettingCompoment extends Component {
|
|||
</Paper>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
className={classes.sectionTitle}
|
||||
variant="subtitle2"
|
||||
>
|
||||
{t("setting.aboutCloudreve")}
|
||||
</Typography>
|
||||
<Paper>
|
||||
<List className={classes.desenList}>
|
||||
<ListItem
|
||||
button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
"https://github.com/cloudreve/cloudreve"
|
||||
)
|
||||
}
|
||||
>
|
||||
<ListItemIcon className={classes.iconFix}>
|
||||
<GitHub />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("setting.githubRepo")} />
|
||||
|
||||
<ListItemSecondaryAction
|
||||
className={classes.flexContainer}
|
||||
>
|
||||
<RightIcon
|
||||
className={classes.rightIconWithText}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem
|
||||
button
|
||||
onClick={() =>
|
||||
window.open("https://cloudreve.org")
|
||||
}
|
||||
>
|
||||
<ListItemIcon className={classes.iconFix}>
|
||||
<Home />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("setting.homepage")} />
|
||||
|
||||
<ListItemSecondaryAction
|
||||
className={classes.flexContainer}
|
||||
>
|
||||
<RightIcon
|
||||
className={classes.rightIconWithText}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
|
||||
<div className={classes.paddingBottom} />
|
||||
</div>
|
||||
<BindPhone
|
||||
phone={this.state.settings.phone}
|
||||
onClose={this.handleClose}
|
||||
open={this.state.bindPhone}
|
||||
/>
|
||||
<TimeZoneDialog
|
||||
onClose={() => this.setState({ changeTimeZone: false })}
|
||||
open={this.state.changeTimeZone}
|
||||
|
|
@ -1193,6 +1335,25 @@ class UserSettingCompoment extends Component {
|
|||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Dialog
|
||||
open={this.state.groupBackModal}
|
||||
onClose={this.handleClose}
|
||||
>
|
||||
<DialogTitle>
|
||||
{t("vas.cancelSubscriptionTitle")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{t("vas.cancelSubscriptionWarning")}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color="default">
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button onClick={this.doChangeGroup} color="primary">
|
||||
{t("ok", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Dialog
|
||||
open={this.state.changePassword}
|
||||
onClose={this.handleClose}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { makeStyles, Typography } from "@material-ui/core";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
|
|
@ -18,8 +18,10 @@ import IconButton from "@material-ui/core/IconButton";
|
|||
import { Cloud, CloudOff, Delete } from "@material-ui/icons";
|
||||
import CreateWebDAVAccount from "../Modals/CreateWebDAVAccount";
|
||||
import TimeAgo from "timeago-react";
|
||||
import CreateWebDAVMount from "../Modals/CreateWebDAVMount";
|
||||
import Link from "@material-ui/core/Link";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { useLocation } from "react-router";
|
||||
import Nothing from "../Placeholder/Nothing";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AppPromotion from "./AppPromotion";
|
||||
|
|
@ -59,9 +61,14 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
export default function WebDAV() {
|
||||
const { t } = useTranslation();
|
||||
const [tab, setTab] = useState(0);
|
||||
const query = parseInt(
|
||||
new URLSearchParams(useLocation().search).get("tab")
|
||||
);
|
||||
const [tab, setTab] = useState(query ? query : 0);
|
||||
const [create, setCreate] = useState(false);
|
||||
const [mount, setMount] = useState(false);
|
||||
const [accounts, setAccounts] = useState([]);
|
||||
const [folders, setFolders] = useState([]);
|
||||
|
||||
const appPromotion = useSelector((state) => state.siteConfig.app_promotion);
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -89,6 +96,7 @@ export default function WebDAV() {
|
|||
API.get("/webdav/accounts")
|
||||
.then((response) => {
|
||||
setAccounts(response.data.accounts);
|
||||
setFolders(response.data.folders);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
|
|
@ -114,6 +122,21 @@ export default function WebDAV() {
|
|||
});
|
||||
};
|
||||
|
||||
const deleteMount = (id) => {
|
||||
const folder = folders[id];
|
||||
API.delete("/webdav/mount/" + folder.id)
|
||||
.then(() => {
|
||||
let folderCopy = [...folders];
|
||||
folderCopy = folderCopy.filter((v, i) => {
|
||||
return i !== id;
|
||||
});
|
||||
setFolders(folderCopy);
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
};
|
||||
|
||||
const toggleAccountReadonly = (id) => {
|
||||
const account = accounts[id];
|
||||
API.patch("/webdav/accounts", {
|
||||
|
|
@ -170,6 +193,20 @@ export default function WebDAV() {
|
|||
});
|
||||
};
|
||||
|
||||
const addMount = (mountInfo) => {
|
||||
setMount(false);
|
||||
API.post("/webdav/mount", {
|
||||
path: mountInfo.path,
|
||||
policy: mountInfo.policy,
|
||||
})
|
||||
.then(() => {
|
||||
loadList();
|
||||
})
|
||||
.catch((error) => {
|
||||
ToggleSnackbar("top", "right", error.message, "error");
|
||||
});
|
||||
};
|
||||
|
||||
const classes = useStyles();
|
||||
const user = Auth.GetUser();
|
||||
|
||||
|
|
@ -180,6 +217,11 @@ export default function WebDAV() {
|
|||
open={create}
|
||||
onClose={() => setCreate(false)}
|
||||
/>
|
||||
<CreateWebDAVMount
|
||||
callback={addMount}
|
||||
open={mount}
|
||||
onClose={() => setMount(false)}
|
||||
/>
|
||||
<Typography color="textSecondary" variant="h4">
|
||||
{t("navbar.connect")}
|
||||
</Typography>
|
||||
|
|
@ -192,6 +234,7 @@ export default function WebDAV() {
|
|||
aria-label="disabled tabs example"
|
||||
>
|
||||
<Tab label={t("setting.webdavAccounts")} />
|
||||
<Tab label={t("vas.mountPolicy")} />
|
||||
{appPromotion && <Tab label={t("setting.iOSApp")} />}
|
||||
</Tabs>
|
||||
<div className={classes.cardContent}>
|
||||
|
|
@ -379,7 +422,71 @@ export default function WebDAV() {
|
|||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{tab === 1 && <AppPromotion />}
|
||||
{tab === 1 && (
|
||||
<div>
|
||||
<Alert severity="info">
|
||||
{t("vas.mountDescription")}
|
||||
</Alert>
|
||||
<TableContainer className={classes.tableContainer}>
|
||||
<Table
|
||||
size="small"
|
||||
className={classes.table}
|
||||
aria-label="simple table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
{t("fileManager.folders")}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{t("fileManager.storagePolicy")}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{t("setting.action")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{folders.map((row, id) => (
|
||||
<TableRow key={id}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
>
|
||||
{row.name}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{row.policy_name}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
size={"small"}
|
||||
onClick={() =>
|
||||
deleteMount(id)
|
||||
}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{folders.length === 0 && (
|
||||
<Nothing primary={t("setting.listEmpty")} />
|
||||
)}
|
||||
</TableContainer>
|
||||
<Button
|
||||
onClick={() => setMount(true)}
|
||||
className={classes.create}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
{t("vas.mountNewFolder")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{tab === 2 && <AppPromotion />}
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,18 +92,18 @@ export default function SearchResult() {
|
|||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [shareList, setShareList] = useState([]);
|
||||
const [orderBy, setOrderBy] = useState("created_at DESC");
|
||||
|
||||
const ToggleSnackbar = useCallback(
|
||||
(vertical, horizontal, msg, color) =>
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [shareList, setShareList] = useState([]);
|
||||
const [orderBy, setOrderBy] = useState("created_at DESC");
|
||||
|
||||
const search = useCallback((keywords, page, orderBy) => {
|
||||
const search = (keywords, page, orderBy) => {
|
||||
const order = orderBy.split(" ");
|
||||
API.get(
|
||||
"/share/search?page=" +
|
||||
|
|
@ -122,7 +122,7 @@ export default function SearchResult() {
|
|||
.catch(() => {
|
||||
ToggleSnackbar("top", "right", t("listLoadingError"), "error");
|
||||
});
|
||||
}, []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const keywords = query.get("keywords");
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export default function SharePreload() {
|
|||
} else {
|
||||
SetSubTitle();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [share, SetSubTitle, ToggleSnackbar]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { connect, useSelector } from "react-redux";
|
||||
import { sizeToString, vhCheck } from "../../utils";
|
||||
import { isPreviewable } from "../../config";
|
||||
import { Button, Typography, withStyles } from "@material-ui/core";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import TypeIcon from "../FileManager/TypeIcon";
|
||||
import Auth from "../../middleware/Auth";
|
||||
import PurchaseShareDialog from "../Modals/PurchaseShare";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import Creator from "./Creator";
|
||||
import pathHelper from "../../utils/page";
|
||||
import Report from "../Modals/Report";
|
||||
import {
|
||||
openMusicDialog,
|
||||
openResaveDialog,
|
||||
|
|
@ -18,6 +20,7 @@ import {
|
|||
toggleSnackbar,
|
||||
} from "../../redux/explorer";
|
||||
import { startDownload } from "../../redux/explorer/action";
|
||||
import { trySharePurchase } from "../../redux/explorer/async";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
vhCheck();
|
||||
|
|
@ -64,7 +67,7 @@ const styles = (theme) => ({
|
|||
|
||||
box: {
|
||||
width: "100%",
|
||||
maxWidth: 440,
|
||||
maxWidth: 490,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: "0 8px 16px rgba(29,39,55,.25)",
|
||||
|
|
@ -121,6 +124,7 @@ const mapDispatchToProps = (dispatch) => {
|
|||
startDownload: (share, file) => {
|
||||
dispatch(startDownload(share, file));
|
||||
},
|
||||
trySharePurchase: (share) => dispatch(trySharePurchase(share)),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -133,6 +137,7 @@ class SharedFileCompoment extends Component {
|
|||
open: false,
|
||||
purchaseCallback: null,
|
||||
loading: false,
|
||||
openReport: false,
|
||||
};
|
||||
|
||||
downloaded = false;
|
||||
|
|
@ -162,8 +167,8 @@ class SharedFileCompoment extends Component {
|
|||
case "msDoc":
|
||||
this.props.history.push(
|
||||
this.props.share.key +
|
||||
"/doc?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
"/doc?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
);
|
||||
return;
|
||||
case "audio":
|
||||
|
|
@ -176,36 +181,36 @@ class SharedFileCompoment extends Component {
|
|||
case "video":
|
||||
this.props.history.push(
|
||||
this.props.share.key +
|
||||
"/video?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
"/video?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
);
|
||||
return;
|
||||
case "edit":
|
||||
this.props.history.push(
|
||||
this.props.share.key +
|
||||
"/text?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
"/text?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
);
|
||||
return;
|
||||
case "pdf":
|
||||
this.props.history.push(
|
||||
this.props.share.key +
|
||||
"/pdf?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
"/pdf?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
);
|
||||
return;
|
||||
case "code":
|
||||
this.props.history.push(
|
||||
this.props.share.key +
|
||||
"/code?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
"/code?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
);
|
||||
return;
|
||||
case "epub":
|
||||
this.props.history.push(
|
||||
this.props.share.key +
|
||||
"/epub?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
"/epub?name=" +
|
||||
encodeURIComponent(this.props.share.source.name)
|
||||
);
|
||||
return;
|
||||
default:
|
||||
|
|
@ -219,24 +224,33 @@ class SharedFileCompoment extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
scoreHandler = (callback) => (event) => {
|
||||
this.props.trySharePurchase(this.props.share).then(() => callback());
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.setSelectedTarget([]);
|
||||
}
|
||||
|
||||
scoreHandle = (callback) => (event) => {
|
||||
callback(event);
|
||||
};
|
||||
|
||||
download = () => {
|
||||
this.props.startDownload(this.props.share, null);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, t } = this.props;
|
||||
const user = Auth.GetUser();
|
||||
const isLogin = Auth.Check();
|
||||
|
||||
return (
|
||||
<div className={classes.layout}>
|
||||
<Modals />
|
||||
<ImgPreview />
|
||||
<PurchaseShareDialog />
|
||||
<Report
|
||||
open={this.state.openReport}
|
||||
share={this.props.share}
|
||||
onClose={() => this.setState({ openReport: false })}
|
||||
/>
|
||||
<div className={classes.box}>
|
||||
<Creator share={this.props.share} />
|
||||
<Divider />
|
||||
|
|
@ -258,18 +272,34 @@ class SharedFileCompoment extends Component {
|
|||
<Divider />
|
||||
<div className={classes.boxFooter}>
|
||||
<div className={classes.actionLeft}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
this.props.openResave(this.props.share.key)
|
||||
}
|
||||
color="secondary"
|
||||
>
|
||||
{t("vas.saveToMyFiles")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
this.setState({ openReport: true })
|
||||
}
|
||||
color="secondary"
|
||||
>
|
||||
{t("vas.report")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classes.actions}>
|
||||
{this.props.share.preview && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={this.scoreHandle(this.preview)}
|
||||
onClick={this.scoreHandler(this.preview)}
|
||||
disabled={this.state.loading}
|
||||
>
|
||||
{t("share.preview")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.actions}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
|
|
@ -278,6 +308,15 @@ class SharedFileCompoment extends Component {
|
|||
disabled={this.state.loading}
|
||||
>
|
||||
{t("fileManager.download")}
|
||||
{this.props.share.score > 0 &&
|
||||
(!isLogin || !user.group.shareFree) &&
|
||||
t("vas.creditPrice", {
|
||||
num: this.props.share.score,
|
||||
})}
|
||||
{this.props.share.score > 0 &&
|
||||
isLogin &&
|
||||
user.group.shareFree &&
|
||||
t("vas.creditFree")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ export async function s3LikeUploadChunk(
|
|||
onProgress: (p: Progress) => void,
|
||||
cancel: CancelToken
|
||||
): Promise<string> {
|
||||
const res = await request<string>(url, {
|
||||
const res = await request<string>(decodeURIComponent(url), {
|
||||
method: "put",
|
||||
headers: {
|
||||
"content-type": "application/octet-stream",
|
||||
|
|
|
|||
|
|
@ -28,19 +28,17 @@ const cdBackendConfig = {
|
|||
};
|
||||
|
||||
export function request<T = any>(url: string, config?: AxiosRequestConfig) {
|
||||
return axios
|
||||
.request<T>({ ...baseConfig, ...config, url })
|
||||
.catch((err) => {
|
||||
if (axios.isCancel(err)) {
|
||||
throw new RequestCanceledError();
|
||||
}
|
||||
return axios.request<T>({ ...baseConfig, ...config, url }).catch((err) => {
|
||||
if (axios.isCancel(err)) {
|
||||
throw new RequestCanceledError();
|
||||
}
|
||||
|
||||
if (err instanceof TransformResponseError) {
|
||||
throw err;
|
||||
}
|
||||
if (err instanceof TransformResponseError) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw new HTTPError(err, url);
|
||||
});
|
||||
throw new HTTPError(err, url);
|
||||
});
|
||||
}
|
||||
|
||||
export function requestAPI<T = any>(url: string, config?: AxiosRequestConfig) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,978 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import SdStorage from "@material-ui/icons/SdStorage";
|
||||
import ShopIcon from "@material-ui/icons/ShoppingCart";
|
||||
import PackSelect from "./PackSelect";
|
||||
import SupervisedUserCircle from "@material-ui/icons/SupervisedUserCircle";
|
||||
import StarIcon from "@material-ui/icons/StarBorder";
|
||||
import LocalPlay from "@material-ui/icons/LocalPlay";
|
||||
import API from "../../middleware/Api";
|
||||
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
Paper,
|
||||
Tab,
|
||||
Tabs,
|
||||
TextField,
|
||||
Typography,
|
||||
withStyles,
|
||||
} from "@material-ui/core";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormLabel from "@material-ui/core/FormLabel";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import { AccountBalanceWallet } from "@material-ui/icons";
|
||||
import { withRouter } from "react-router";
|
||||
import { toggleSnackbar } from "../../redux/explorer";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import PaymentDialog from "./PaymentDialog";
|
||||
|
||||
const styles = (theme) => ({
|
||||
layout: {
|
||||
width: "auto",
|
||||
marginTop: "50px",
|
||||
marginLeft: theme.spacing(3),
|
||||
marginRight: theme.spacing(3),
|
||||
[theme.breakpoints.up(1100 + theme.spacing(3) * 2)]: {
|
||||
width: 1100,
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
},
|
||||
marginBottom: "50px",
|
||||
},
|
||||
|
||||
gird: {
|
||||
marginTop: "30px",
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2),
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
title: {
|
||||
marginTop: "30px",
|
||||
marginBottom: "30px",
|
||||
},
|
||||
button: {
|
||||
margin: theme.spacing(1),
|
||||
},
|
||||
action: {
|
||||
textAlign: "right",
|
||||
marginTop: "20px",
|
||||
},
|
||||
textField: {
|
||||
marginLeft: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
width: 70,
|
||||
textAlign: "center!important",
|
||||
},
|
||||
priceShow: {
|
||||
color: theme.palette.secondary.main,
|
||||
fontSize: "30px",
|
||||
},
|
||||
cardHeader: {
|
||||
backgroundColor:
|
||||
theme.palette.type === "dark"
|
||||
? theme.palette.background.default
|
||||
: theme.palette.grey[200],
|
||||
},
|
||||
cardPricing: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "baseline",
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
cardActions: {
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
redeemContainer: {
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
marginLeft: "50px",
|
||||
marginRight: "50px",
|
||||
width: "auto",
|
||||
},
|
||||
marginTop: "50px",
|
||||
marginBottom: "50px",
|
||||
},
|
||||
doRedeem: {
|
||||
textAlign: "right",
|
||||
},
|
||||
payMethod: {
|
||||
marginTop: theme.spacing(4),
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
});
|
||||
const mapStateToProps = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
toggleSnackbar: (vertical, horizontal, msg, color) => {
|
||||
dispatch(toggleSnackbar(vertical, horizontal, msg, color));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
class BuyQuotaCompoment extends Component {
|
||||
IntervalId = null;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const tab = new URLSearchParams(this.props.location.search);
|
||||
const index = tab.get("tab");
|
||||
|
||||
this.state = {
|
||||
value: index ? parseInt(index) : 0,
|
||||
selectedPack: -1,
|
||||
selectedGroup: -1,
|
||||
times: 1,
|
||||
scoreNum: 1,
|
||||
loading: false,
|
||||
redeemCode: "",
|
||||
dialog: null,
|
||||
payment: {
|
||||
type: "",
|
||||
img: "",
|
||||
},
|
||||
scorePrice: 0,
|
||||
redeemInfo: null,
|
||||
packs: [],
|
||||
groups: [],
|
||||
alipay: false,
|
||||
payjs: false,
|
||||
wechat: false,
|
||||
packPayMethod: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.clearInterval(this.IntervalId);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
API.get("/vas/product")
|
||||
.then((response) => {
|
||||
this.setState({
|
||||
packs: response.data.packs,
|
||||
groups: response.data.groups,
|
||||
alipay: response.data.alipay,
|
||||
payjs: response.data.payjs,
|
||||
wechat: response.data.wechat,
|
||||
custom: response.data.custom
|
||||
? response.data.custom_name
|
||||
: null,
|
||||
scorePrice: response.data.score_price,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
confirmRedeem = () => {
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
API.post("/vas/redeem/" + this.state.redeemCode)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
dialog: "success",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
doRedeem = () => {
|
||||
if (this.state.redeemCode === "") {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
this.props.t("vas.pleaseInputGiftCode"),
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
API.get("/vas/redeem/" + this.state.redeemCode)
|
||||
.then((response) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
dialog: "redeem",
|
||||
redeemInfo: response.data,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
buyPack = (packType) => {
|
||||
if (packType === "pack" && this.state.selectedPack === -1) {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
this.props.t("vas.pleaseSelectAStoragePack"),
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
API.post("/vas/order", {
|
||||
action: packType,
|
||||
method: this.state.packPayMethod,
|
||||
id:
|
||||
packType === "score"
|
||||
? 1
|
||||
: packType === "pack"
|
||||
? this.state.packs[this.state.selectedPack].id
|
||||
: this.state.groups[this.state.selectedGroup].id,
|
||||
num:
|
||||
packType === "score"
|
||||
? parseInt(this.state.scoreNum)
|
||||
: parseInt(this.state.times),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.data.payment) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
dialog: "success",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (response.data.qr_code) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
dialog: "qr",
|
||||
payment: {
|
||||
type: this.state.packPayMethod,
|
||||
img: response.data.qr_code,
|
||||
},
|
||||
});
|
||||
this.IntervalId = window.setInterval(() => {
|
||||
this.querryLoop(response.data.id);
|
||||
}, 3000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
querryLoop = (id) => {
|
||||
API.get("/vas/order/" + id)
|
||||
.then((response) => {
|
||||
if (response.data === 1) {
|
||||
this.setState({
|
||||
dialog: "success",
|
||||
});
|
||||
window.clearInterval(this.IntervalId);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.props.toggleSnackbar(
|
||||
"top",
|
||||
"right",
|
||||
error.message,
|
||||
"error"
|
||||
);
|
||||
window.clearInterval(this.IntervalId);
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = (event, value) => {
|
||||
this.setState({
|
||||
packPayMethod:
|
||||
this.state.packPayMethod === "score"
|
||||
? null
|
||||
: this.state.packPayMethod,
|
||||
});
|
||||
this.setState({ value });
|
||||
};
|
||||
|
||||
handleChangeIndex = (index) => {
|
||||
this.setState({ value: index });
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
window.clearInterval(this.IntervalId);
|
||||
this.setState({
|
||||
dialog: null,
|
||||
});
|
||||
};
|
||||
|
||||
handleTexyChange = (name) => (event) => {
|
||||
this.setState({ [name]: event.target.value });
|
||||
};
|
||||
|
||||
selectPack = (id) => {
|
||||
this.setState({
|
||||
selectedPack: id,
|
||||
packPayMethod:
|
||||
this.state.packPayMethod === "score"
|
||||
? null
|
||||
: this.state.packPayMethod,
|
||||
});
|
||||
};
|
||||
|
||||
selectGroup = (id) => {
|
||||
this.setState({
|
||||
selectedGroup: id,
|
||||
dialog: "buyGroup",
|
||||
packPayMethod:
|
||||
this.state.packPayMethod === "score"
|
||||
? null
|
||||
: this.state.packPayMethod,
|
||||
});
|
||||
};
|
||||
|
||||
selectPackPayMethod = (event) => {
|
||||
this.setState({
|
||||
packPayMethod: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, t } = this.props;
|
||||
|
||||
const methodSelect = (
|
||||
<div>
|
||||
<FormLabel>{t("vas.selectPaymentMethod")}</FormLabel>
|
||||
<RadioGroup
|
||||
name="spacing"
|
||||
aria-label="spacing"
|
||||
value={this.state.packPayMethod}
|
||||
onChange={this.selectPackPayMethod}
|
||||
row
|
||||
>
|
||||
{!this.state.alipay &&
|
||||
!this.state.payjs &&
|
||||
this.state.value === 0 &&
|
||||
(this.state.selectedPack === -1 ||
|
||||
this.state.packs[this.state.selectedPack].score ===
|
||||
0) &&
|
||||
this.state.value === 1 &&
|
||||
(this.state.selectedGroup === -1 ||
|
||||
this.state.groups[this.state.selectedGroup]
|
||||
.score === 0) && (
|
||||
<Typography>
|
||||
{t("vas.noAvailableMethod")}
|
||||
</Typography>
|
||||
)}
|
||||
{this.state.alipay && (
|
||||
<FormControlLabel
|
||||
value={"alipay"}
|
||||
control={<Radio />}
|
||||
label={t("vas.alipay")}
|
||||
/>
|
||||
)}
|
||||
{this.state.payjs && (
|
||||
<FormControlLabel
|
||||
value={"payjs"}
|
||||
control={<Radio />}
|
||||
label={t("vas.wechatPay")}
|
||||
/>
|
||||
)}
|
||||
{this.state.wechat && (
|
||||
<FormControlLabel
|
||||
value={"wechat"}
|
||||
control={<Radio />}
|
||||
label={t("vas.wechatPay")}
|
||||
/>
|
||||
)}
|
||||
{this.state.custom && (
|
||||
<FormControlLabel
|
||||
value={"custom"}
|
||||
control={<Radio />}
|
||||
label={this.state.custom}
|
||||
/>
|
||||
)}
|
||||
{this.state.value === 0 &&
|
||||
this.state.selectedPack !== -1 &&
|
||||
this.state.packs[this.state.selectedPack].score !==
|
||||
0 && (
|
||||
<FormControlLabel
|
||||
value={"score"}
|
||||
control={<Radio />}
|
||||
label={t("vas.payByCredits")}
|
||||
/>
|
||||
)}
|
||||
{this.state.value === 1 &&
|
||||
this.state.selectedGroup !== -1 &&
|
||||
this.state.groups[this.state.selectedGroup].score !==
|
||||
0 && (
|
||||
<FormControlLabel
|
||||
value={"score"}
|
||||
control={<Radio />}
|
||||
label={t("vas.payByCredits")}
|
||||
/>
|
||||
)}
|
||||
</RadioGroup>
|
||||
<div>
|
||||
{this.state.value !== 2 && (
|
||||
<FormLabel>{t("vas.purchaseDuration")}</FormLabel>
|
||||
)}
|
||||
{this.state.value === 2 && (
|
||||
<FormLabel>{t("vas.creditsNum")}</FormLabel>
|
||||
)}
|
||||
</div>
|
||||
{this.state.value !== 2 && (
|
||||
<TextField
|
||||
className={classes.textField}
|
||||
type="number"
|
||||
inputProps={{
|
||||
min: "1",
|
||||
max: "99",
|
||||
step: "1",
|
||||
}}
|
||||
value={this.state.times}
|
||||
onChange={this.handleTexyChange("times")}
|
||||
/>
|
||||
)}
|
||||
{this.state.value === 2 && (
|
||||
<TextField
|
||||
className={classes.textField}
|
||||
type="number"
|
||||
inputProps={{
|
||||
min: "1",
|
||||
step: "1",
|
||||
max: "9999999",
|
||||
}}
|
||||
value={this.state.scoreNum}
|
||||
onChange={this.handleTexyChange("scoreNum")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes.layout}>
|
||||
<Typography
|
||||
color="textSecondary"
|
||||
className={classes.title}
|
||||
variant="h3"
|
||||
>
|
||||
{t("vas.store")}
|
||||
</Typography>
|
||||
<AppBar position="static">
|
||||
<Tabs
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab
|
||||
label={t("vas.storagePacks")}
|
||||
icon={<SdStorage />}
|
||||
/>
|
||||
<Tab
|
||||
label={t("vas.membership")}
|
||||
icon={<SupervisedUserCircle />}
|
||||
/>
|
||||
{this.state.scorePrice > 0 && (
|
||||
<Tab
|
||||
label={t("vas.buyCredits")}
|
||||
icon={<AccountBalanceWallet />}
|
||||
/>
|
||||
)}
|
||||
<Tab
|
||||
label={t("vas.useGiftCode")}
|
||||
icon={<LocalPlay />}
|
||||
/>
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
{this.state.value === 0 && (
|
||||
<Paper className={classes.paper} square={true}>
|
||||
<Grid container spacing={3}>
|
||||
{this.state.packs.map((value, id) => (
|
||||
<Grid item xs={12} md={3} key={id}>
|
||||
<PackSelect
|
||||
pack={value}
|
||||
onSelect={() => this.selectPack(id)}
|
||||
active={this.state.selectedPack === id}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
className={classes.payMethod}
|
||||
spacing={1}
|
||||
>
|
||||
<Grid sm={6} xs={12}>
|
||||
{methodSelect}
|
||||
</Grid>
|
||||
<Grid sm={6} xs={12}>
|
||||
<div className={classes.action}>
|
||||
<div>
|
||||
{t("vas.subtotal")}
|
||||
{this.state.packPayMethod !==
|
||||
"score" && (
|
||||
<span className={classes.priceShow}>
|
||||
¥
|
||||
{this.state.selectedPack ===
|
||||
-1 && <span>--</span>}
|
||||
{this.state.selectedPack !==
|
||||
-1 &&
|
||||
this.state.times <= 99 &&
|
||||
this.state.times >= 1 && (
|
||||
<span>
|
||||
{(
|
||||
(this.state
|
||||
.packs[
|
||||
this.state
|
||||
.selectedPack
|
||||
].price /
|
||||
100) *
|
||||
this.state.times
|
||||
).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{this.state.packPayMethod ===
|
||||
"score" && (
|
||||
<span className={classes.priceShow}>
|
||||
{this.state.selectedPack ===
|
||||
-1 && <span>--</span>}
|
||||
{this.state.selectedPack !==
|
||||
-1 &&
|
||||
this.state.times <= 99 &&
|
||||
this.state.times >= 1 && (
|
||||
<span>
|
||||
{t(
|
||||
"vas.creditsTotalNum",
|
||||
{
|
||||
num:
|
||||
this
|
||||
.state
|
||||
.packs[
|
||||
this
|
||||
.state
|
||||
.selectedPack
|
||||
]
|
||||
.score *
|
||||
this
|
||||
.state
|
||||
.times,
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
className={classes.button}
|
||||
disabled={
|
||||
this.state.loading ||
|
||||
this.state.packPayMethod ===
|
||||
null
|
||||
}
|
||||
onClick={() => this.buyPack("pack")}
|
||||
>
|
||||
<ShopIcon /> {t("vas.checkoutNow")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)}
|
||||
{this.state.value === 1 && (
|
||||
<Paper className={classes.paper} square={true}>
|
||||
<Grid container spacing={5} alignItems="flex-end">
|
||||
{this.state.groups.map((tier, id) => (
|
||||
// Enterprise card is full width at sm breakpoint
|
||||
<Grid item key={id} xs={12} sm={6} md={4}>
|
||||
<Card>
|
||||
<CardHeader
|
||||
title={tier.name}
|
||||
subheader={
|
||||
tier.highlight
|
||||
? t("vas.recommended")
|
||||
: null
|
||||
}
|
||||
titleTypographyProps={{
|
||||
align: "center",
|
||||
}}
|
||||
subheaderTypographyProps={{
|
||||
align: "center",
|
||||
}}
|
||||
action={
|
||||
tier.highlight ? (
|
||||
<StarIcon />
|
||||
) : null
|
||||
}
|
||||
className={classes.cardHeader}
|
||||
/>
|
||||
<CardContent>
|
||||
<div
|
||||
className={classes.cardPricing}
|
||||
>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h3"
|
||||
color="textPrimary"
|
||||
>
|
||||
¥{tier.price / 100}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
color="textSecondary"
|
||||
>
|
||||
/
|
||||
{t("vas.days", {
|
||||
num: Math.ceil(
|
||||
tier.time / 86400
|
||||
),
|
||||
})}
|
||||
</Typography>
|
||||
</div>
|
||||
{tier.des.map((line) => (
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
align="center"
|
||||
key={line}
|
||||
>
|
||||
{line}
|
||||
</Typography>
|
||||
))}
|
||||
</CardContent>
|
||||
<CardActions
|
||||
className={classes.cardActions}
|
||||
>
|
||||
<Button
|
||||
fullWidth
|
||||
variant={
|
||||
tier.highlight
|
||||
? "contained"
|
||||
: "outlined"
|
||||
}
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
this.selectGroup(id)
|
||||
}
|
||||
>
|
||||
{t("vas.checkoutNow")}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{this.state.value === 2 && (
|
||||
<Paper className={classes.paper} square={true}>
|
||||
<Grid
|
||||
container
|
||||
className={classes.payMethod}
|
||||
spacing={1}
|
||||
>
|
||||
<Grid sm={6} xs={12}>
|
||||
{methodSelect}
|
||||
</Grid>
|
||||
<Grid sm={6} xs={12}>
|
||||
<div className={classes.action}>
|
||||
<div>
|
||||
{t("vas.subtotal")}
|
||||
{this.state.packPayMethod !==
|
||||
"score" && (
|
||||
<span className={classes.priceShow}>
|
||||
¥
|
||||
<span>
|
||||
{(
|
||||
(this.state.scorePrice /
|
||||
100) *
|
||||
this.state.scoreNum
|
||||
).toFixed(2)}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
className={classes.button}
|
||||
disabled={
|
||||
this.state.loading ||
|
||||
this.state.packPayMethod ===
|
||||
null
|
||||
}
|
||||
onClick={() =>
|
||||
this.buyPack("score")
|
||||
}
|
||||
>
|
||||
<ShopIcon /> {t("vas.checkoutNow")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{this.state.value === 3 && (
|
||||
<Paper className={classes.paper} square={true}>
|
||||
<div className={classes.redeemContainer}>
|
||||
<TextField
|
||||
id="standard-name"
|
||||
label={t("vas.enterGiftCode")}
|
||||
value={this.state.redeemCode}
|
||||
onChange={this.handleTexyChange("redeemCode")}
|
||||
margin="normal"
|
||||
inputProps={{
|
||||
style: { textTransform: "uppercase" },
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<div className={classes.doRedeem}>
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
className={classes.button}
|
||||
disabled={this.state.loading}
|
||||
onClick={this.doRedeem}
|
||||
>
|
||||
{t("login.continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
)}
|
||||
<PaymentDialog
|
||||
open={this.state.dialog === "qr"}
|
||||
payment={this.state.payment}
|
||||
handleClose={this.handleClose}
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
open={this.state.dialog === "success"}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{t("vas.paymentCompleted")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{t("vas.productDelivered")}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color="primary">
|
||||
{t("close", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={this.state.dialog === "redeem"}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{t("vas.confirmRedeem")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{this.state.redeemInfo !== null && (
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<Typography variant="subtitle1">
|
||||
{t("vas.productName")}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{this.state.redeemInfo.name}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1">
|
||||
{this.state.redeemInfo.type === 2
|
||||
? t("vas.qyt")
|
||||
: t("vas.duration")}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{this.state.redeemInfo.type === 2 && (
|
||||
<>{this.state.redeemInfo.num}</>
|
||||
)}
|
||||
{this.state.redeemInfo.type !== 2 && (
|
||||
<>
|
||||
{t("vas.days", {
|
||||
num:
|
||||
Math.ceil(
|
||||
this.state.redeemInfo
|
||||
.time / 86400
|
||||
) *
|
||||
this.state.redeemInfo.num,
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</DialogContentText>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose}>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={this.confirmRedeem}
|
||||
color="primary"
|
||||
disabled={this.state.loading}
|
||||
>
|
||||
{t("vas.confirmRedeem")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={this.state.dialog === "buyGroup"}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{t("vas.subscribe")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{t("vas.selected")}
|
||||
{this.state.selectedGroup !== -1 &&
|
||||
this.state.groups[this.state.selectedGroup]
|
||||
.name}
|
||||
</DialogContentText>
|
||||
{methodSelect}
|
||||
<div>
|
||||
{t("vas.subtotal")}
|
||||
{this.state.packPayMethod !== "score" && (
|
||||
<span className={classes.priceShow}>
|
||||
¥
|
||||
{this.state.selectedGroup === -1 && (
|
||||
<span>--</span>
|
||||
)}
|
||||
{this.state.selectedGroup !== -1 &&
|
||||
this.state.times <= 99 &&
|
||||
this.state.times >= 1 && (
|
||||
<span>
|
||||
{(
|
||||
(this.state.groups[
|
||||
this.state.selectedGroup
|
||||
].price /
|
||||
100) *
|
||||
this.state.times
|
||||
).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{this.state.packPayMethod === "score" && (
|
||||
<span className={classes.priceShow}>
|
||||
{this.state.selectedGroup === -1 && (
|
||||
<span>--</span>
|
||||
)}
|
||||
{this.state.selectedGroup !== -1 &&
|
||||
this.state.times <= 99 &&
|
||||
this.state.times >= 1 && (
|
||||
<span>
|
||||
{t("vas.creditsTotalNum", {
|
||||
num:
|
||||
this.state.groups[
|
||||
this.state
|
||||
.selectedGroup
|
||||
].score *
|
||||
this.state.times,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={this.handleClose}
|
||||
disabled={this.state.loading}
|
||||
>
|
||||
{t("cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={
|
||||
this.state.loading ||
|
||||
this.state.packPayMethod === null
|
||||
}
|
||||
onClick={() => this.buyPack("group")}
|
||||
color="primary"
|
||||
>
|
||||
{t("vas.checkoutNow")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const BuyQuota = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withStyles(styles)(withRouter(withTranslation()(BuyQuotaCompoment))));
|
||||
|
||||
export default BuyQuota;
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import React, { Component } from "react";
|
||||
import classNames from "classnames";
|
||||
import { ButtonBase, Typography, withStyles } from "@material-ui/core";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
const styles = (theme) => ({
|
||||
container: {
|
||||
boxShadow: "0 0 0 1px #e6e9eb",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
transition: "box-shadow .5s",
|
||||
width: "100%",
|
||||
display: "block",
|
||||
},
|
||||
active: {
|
||||
boxShadow: "0 0 0 3px " + theme.palette.primary.main,
|
||||
},
|
||||
boxHead: {
|
||||
textAlign: "center",
|
||||
padding: "10px 10px 10px",
|
||||
borderBottom: "1px solid #e6e9eb",
|
||||
color: theme.palette.text.main,
|
||||
width: "100%",
|
||||
},
|
||||
price: {
|
||||
fontSize: "33px",
|
||||
fontWeight: "500",
|
||||
lineHeight: "40px",
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
priceWithScore: {
|
||||
fontSize: "23px",
|
||||
fontWeight: "500",
|
||||
lineHeight: "40px",
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
packName: {
|
||||
marginTop: "5px",
|
||||
marginBottom: "5px",
|
||||
},
|
||||
boxBottom: {
|
||||
color: theme.palette.text.main,
|
||||
textAlign: "center",
|
||||
padding: "5px",
|
||||
},
|
||||
});
|
||||
|
||||
class PackSelect extends Component {
|
||||
render() {
|
||||
const { classes, pack, t } = this.props;
|
||||
return (
|
||||
<ButtonBase
|
||||
className={classNames(classes.container, {
|
||||
[classes.active]: this.props.active,
|
||||
})}
|
||||
onClick={this.props.onSelect}
|
||||
>
|
||||
<div className={classes.boxHead}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
className={classes.packName}
|
||||
>
|
||||
{pack.name}
|
||||
</Typography>
|
||||
{pack.score === 0 && (
|
||||
<Typography className={classes.price}>
|
||||
¥{(pack.price / 100).toFixed(2)}
|
||||
</Typography>
|
||||
)}
|
||||
{pack.score !== 0 && (
|
||||
<Typography className={classes.priceWithScore}>
|
||||
¥{(pack.price / 100).toFixed(2)} /
|
||||
{t("vas.creditsTotalNum", {
|
||||
num: pack.score,
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.boxBottom}>
|
||||
<Typography>
|
||||
{t("vas.validDurationDays", {
|
||||
num: Math.ceil(pack.time / 86400),
|
||||
})}
|
||||
</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(withTranslation()(PackSelect));
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
makeStyles,
|
||||
} from "@material-ui/core";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
codeContainer: {
|
||||
textAlign: "center",
|
||||
marginTop: "20px",
|
||||
},
|
||||
}));
|
||||
|
||||
export default function PaymentDialog({ open, handleClose, payment }) {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{t("vas.paymentQrcode")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{payment.type === "alipay" && t("vas.qrcodeAlipay")}
|
||||
{payment.type === "payjs" && t("vas.qrcodeWechat")}
|
||||
{payment.type === "custom" && t("vas.qrcodeCustom")}
|
||||
</DialogContentText>
|
||||
<div className={classes.codeContainer}>
|
||||
<QRCodeSVG value={payment.img} />,
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => window.open(payment.img)}>
|
||||
{t("vas.openPaymentLink")}
|
||||
</Button>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
{t("close", { ns: "common" })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue