build: trident-sync prepare
|
@ -1 +0,0 @@
|
|||
Subproject commit 8c7b3853be27df392dff765d39c6c53de09418bd
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a5681f154468fc5c4aac7e4a0ce090cfffc00e9e
|
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
|
@ -0,0 +1,3 @@
|
|||
VITE_APP_API=/api
|
||||
#登录与权限关闭
|
||||
VITE_APP_PM_ENABLED=true
|
|
@ -0,0 +1,2 @@
|
|||
#登录与权限开启
|
||||
VITE_APP_PM_ENABLED=false
|
|
@ -0,0 +1,2 @@
|
|||
#登录与权限开启
|
||||
VITE_APP_PM_ENABLED=true
|
|
@ -0,0 +1,2 @@
|
|||
#登录与权限开启
|
||||
VITE_APP_PM_ENABLED=true
|
|
@ -0,0 +1,3 @@
|
|||
VITE_APP_API=http://www.docmirror.cn:7001/api
|
||||
#登录与权限开启
|
||||
VITE_APP_PM_ENABLED=true
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
.idea
|
|
@ -0,0 +1,76 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
},
|
||||
parser: "vue-eslint-parser",
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
jsxPragma: "React",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
tsx: true
|
||||
}
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier"
|
||||
],
|
||||
rules: {
|
||||
//"max-len": [0, 200, 2, { ignoreUrls: true }],
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
// "@typescript-eslint/no-unused-vars": [
|
||||
// "error",
|
||||
// {
|
||||
// argsIgnorePattern: "^h$",
|
||||
// varsIgnorePattern: "^h$",
|
||||
// },
|
||||
// ],
|
||||
// "no-unused-vars": [
|
||||
// "error",
|
||||
// {
|
||||
// argsIgnorePattern: "^h$",
|
||||
// varsIgnorePattern: "^h$",
|
||||
// },
|
||||
// ],
|
||||
// "vue/custom-event-name-casing": "off",
|
||||
// "no-use-before-define": "off",
|
||||
// "space-before-function-paren": "off",
|
||||
|
||||
// "vue/attributes-order": "off",
|
||||
// "vue/one-component-per-file": "off",
|
||||
// "vue/html-closing-bracket-newline": "off",
|
||||
// "vue/max-attributes-per-line": "off",
|
||||
// "vue/multiline-html-element-content-newline": "off",
|
||||
// "vue/singleline-html-element-content-newline": "off",
|
||||
// "vue/attribute-hyphenation": "off",
|
||||
// "vue/require-default-prop": "off",
|
||||
// "vue/html-self-closing": [
|
||||
// "error",
|
||||
// {
|
||||
// html: {
|
||||
// void: "always",
|
||||
// normal: "never",
|
||||
// component: "always",
|
||||
// },
|
||||
// svg: "always",
|
||||
// math: "always",
|
||||
// },
|
||||
// ],
|
||||
}
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
/stats.html
|
||||
yarn.lock
|
||||
.idea
|
||||
/.idea/
|
||||
yarn-error.log
|
||||
vite-profile.cpuprofile
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
/stats.html
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
|
||||
"trailingComma": "none",
|
||||
"printWidth": 220
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.10.5](https://github.com/fast-crud/fast-crud/compare/v0.10.4...v0.10.5) (2021-07-01)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* fs-admin 与crud demo ([4e6b20f](https://github.com/fast-crud/fast-crud/commit/4e6b20fe19434460853841f371b9fd5f16e5e2d3))
|
||||
* fs-admin纳入子模块 ([2940d30](https://github.com/fast-crud/fast-crud/commit/2940d30f419bf4bde1e8e791f1fbdb9184818285))
|
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,27 @@
|
|||
# fs-admin-antdv
|
||||
|
||||
基于vue3、antdv 的 admin管理后台脚手架
|
||||
更多信息请参考: [fast-crud](https://github.com/fast-crud/fast-crud)
|
||||
# server端
|
||||
|
||||
## fs-server-js
|
||||
https://github.com/fast-crud/fs-server-js
|
||||
|
||||
|
||||
# 感谢
|
||||
|
||||
### 依赖
|
||||
* [vue](https://github.com/vuejs/vue-next)
|
||||
* [vue-router](https://github.com/vuejs/vue-router-next)
|
||||
* [antdv 2x](https://github.com/vueComponent/ant-design-vue)
|
||||
* [vitejs](https://github.com/vitejs/vite)
|
||||
* [pinia](https://github.com/posva/pinia)
|
||||
* [purge-icons](https://github.com/antfu/purge-icons)
|
||||
|
||||
### 参考如下项目
|
||||
* [d2-admin](https://github.com/d2-projects/d2-admin)
|
||||
* [antdv-pro](https://github.com/vueComponent/ant-design-vue-pro)
|
||||
* [vben-admin](https://github.com/anncwb/vue-vben-admin)
|
||||
|
||||
感谢这些优秀的项目
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// import { getThemeVariables } from "ant-design-vue/dist/theme";
|
||||
// import path from "path";
|
||||
// const resolve = path.resolve;
|
||||
export function generateModifyVars(dark = false) {
|
||||
//const modifyVars = getThemeVariables({ dark });
|
||||
// const vars = `${resolve("src/style/theme/index.less")}`;
|
||||
return {
|
||||
//...modifyVars
|
||||
// hack: `true; @import (reference) "${vars}";`
|
||||
};
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import { generate } from "@ant-design/colors";
|
||||
|
||||
export const primaryColor = "#1890ff";
|
||||
|
||||
export const darkMode = "light";
|
||||
|
||||
type Fn = (...arg: any) => any;
|
||||
|
||||
export interface GenerateColorsParams {
|
||||
mixLighten: Fn;
|
||||
mixDarken: Fn;
|
||||
tinycolor: any;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export function generateAntColors(color: string) {
|
||||
return generate(color, {
|
||||
theme: "default"
|
||||
});
|
||||
}
|
||||
|
||||
export function getThemeColors(color?: string) {
|
||||
const tc = color || primaryColor;
|
||||
const colors = generateAntColors(tc);
|
||||
const primary = colors[5];
|
||||
const modeColors = generateAntColors(primary);
|
||||
|
||||
return [...colors, ...modeColors];
|
||||
}
|
||||
|
||||
export function generateColors({ color = primaryColor, mixLighten, mixDarken, tinycolor }: GenerateColorsParams) {
|
||||
const arr = new Array(19).fill(0);
|
||||
const lightens = arr.map((_t, i) => {
|
||||
return mixLighten(color, i / 5);
|
||||
});
|
||||
|
||||
const darkens = arr.map((_t, i) => {
|
||||
return mixDarken(color, i / 5);
|
||||
});
|
||||
|
||||
const alphaColors = arr.map((_t, i) => {
|
||||
return tinycolor(color)
|
||||
.setAlpha(i / 20)
|
||||
.toRgbString();
|
||||
});
|
||||
|
||||
const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, "").replace(/0\./g, "."));
|
||||
|
||||
const tinycolorLightens = arr
|
||||
.map((_t, i) => {
|
||||
return tinycolor(color)
|
||||
.lighten(i * 5)
|
||||
.toHexString();
|
||||
})
|
||||
.filter((item) => item !== "#ffffff");
|
||||
|
||||
const tinycolorDarkens = arr
|
||||
.map((_t, i) => {
|
||||
return tinycolor(color)
|
||||
.darken(i * 5)
|
||||
.toHexString();
|
||||
})
|
||||
.filter((item) => item !== "#000000");
|
||||
return [
|
||||
...lightens,
|
||||
...darkens,
|
||||
...alphaColors,
|
||||
...shortAlphaColors,
|
||||
...tinycolorDarkens,
|
||||
...tinycolorLightens
|
||||
].filter((item) => !item.includes("-"));
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Vite plugin for website theme color switching
|
||||
* https://github.com/anncwb/vite-plugin-theme
|
||||
*/
|
||||
import type { Plugin } from "vite";
|
||||
import path from "path";
|
||||
import { viteThemePlugin, mixLighten, mixDarken, tinycolor, antdDarkThemePlugin } from "vite-plugin-theme";
|
||||
import { getThemeColors, generateColors } from "./theme-colors";
|
||||
import { generateModifyVars } from "./modify-vars";
|
||||
|
||||
export function configThemePlugin(isBuild: boolean): Plugin[] {
|
||||
const colors = generateColors({
|
||||
mixDarken,
|
||||
mixLighten,
|
||||
tinycolor
|
||||
});
|
||||
const colorVariables = [...getThemeColors(), ...colors];
|
||||
const plugin = [
|
||||
viteThemePlugin({
|
||||
// resolveSelector: (s) => {
|
||||
// s = s.trim();
|
||||
// switch (s) {
|
||||
// case ".ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon":
|
||||
// return ".ant-steps-item-icon > .ant-steps-icon";
|
||||
// case ".ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)":
|
||||
// case ".ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover":
|
||||
// case ".ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active":
|
||||
// return s;
|
||||
// case ".ant-steps-item-icon > .ant-steps-icon":
|
||||
// return s;
|
||||
// }
|
||||
// return `[data-theme] ${s}`;
|
||||
// },
|
||||
resolveSelector: (s) => {
|
||||
s = s.trim();
|
||||
if (s === ".ant-btn:hover,.ant-btn:focus") {
|
||||
// console.log("ssss", s);
|
||||
return ".theme-discard-xxxxxxx";
|
||||
}
|
||||
return s;
|
||||
},
|
||||
colorVariables
|
||||
}),
|
||||
antdDarkThemePlugin({
|
||||
preloadFiles: [
|
||||
path.resolve(process.cwd(), "node_modules/ant-design-vue/dist/antd.less"),
|
||||
path.resolve(process.cwd(), "src/style/theme/index.less")
|
||||
],
|
||||
filter: (id) => (isBuild ? !id.endsWith("antd.less") : true),
|
||||
// extractCss: false,
|
||||
darkModifyVars: {
|
||||
...generateModifyVars(true),
|
||||
"text-color": "#c9d1d9",
|
||||
"text-color-base": "#c9d1d9",
|
||||
"component-background": "#151515",
|
||||
// black: '#0e1117',
|
||||
// #8b949e
|
||||
"text-color-secondary": "#8b949e",
|
||||
"border-color-base": "#303030",
|
||||
// 'border-color-split': '#30363d',
|
||||
"item-active-bg": "#111b26",
|
||||
"app-content-background": "rgb(255 255 255 / 4%)"
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
return (plugin as unknown) as Plugin[];
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>antdv-fast-crud</title>
|
||||
<link rel="stylesheet" type="text/css" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="fs-bootstrap">
|
||||
<div class="fs-bootstrap__main">
|
||||
<div class="fs-bootstrap__loading"></div>
|
||||
</div>
|
||||
<div class="fs-bootstrap__footer">
|
||||
<a href="https://github.com/fast-crud/fast-crud" target="_blank">
|
||||
https://github.com/fast-crud/fast-crud
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"name": "@fast-crud/fs-admin-antdv",
|
||||
"version": "1.8.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:pm": "vite --mode pm",
|
||||
"dev:force": "vite --force",
|
||||
"debug": "vite --mode debug",
|
||||
"debug:pm": "vite --mode debugpm",
|
||||
"debug:force": "vite --force --mode debug",
|
||||
"build": "vite build ",
|
||||
"serve": "vite preview",
|
||||
"preview": "vite preview",
|
||||
"pretty-quick": "pretty-quick",
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/",
|
||||
"upgrade": "yarn upgrade-interactive --latest"
|
||||
},
|
||||
"author": "Greper",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons-vue": "^6.0.1",
|
||||
"@fast-crud/fast-crud": "^1.8.1",
|
||||
"@fast-crud/fast-extends": "^1.7.2",
|
||||
"@fast-crud/ui-antdv": "^1.8.1",
|
||||
"@iconify/iconify": "^3.0.1",
|
||||
"@iconify/json": "^2.1.151",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
||||
"ant-design-vue": "^3.2.15",
|
||||
"axios": "^1.2.1",
|
||||
"axios-mock-adapter": "^1.21.2",
|
||||
"base64-js": "^1.5.1",
|
||||
"better-scroll": "^2.5.0",
|
||||
"china-division": "^2.4.0",
|
||||
"core-js": "^3.26.1",
|
||||
"cos-js-sdk-v5": "^1.4.15-beta.0",
|
||||
"cropperjs": "^1.5.13",
|
||||
"deepdash-es": "5.3.5",
|
||||
"highlight.js": "^11.7.0",
|
||||
"humanize-duration": "^3.27.3",
|
||||
"lodash-es": "^4.17.15",
|
||||
"mitt": "^3.0.0",
|
||||
"nanoid": "^4.0.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"pinia": "2.0.28",
|
||||
"qiniu-js": "^3.4.1",
|
||||
"sortablejs": "^1.14.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-cropperjs": "^5.0.0",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.1.5",
|
||||
"vuedraggable": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^23.0.4",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^18.11.15",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"@vitejs/plugin-legacy": "^3.0.1",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||
"@vue/compiler-sfc": "^3.2.45",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"@vue/test-utils": "^2.2.6",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"caller-path": "^4.0.0",
|
||||
"chai": "^4.3.7",
|
||||
"eslint": "8.29.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^6.0.1",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"esno": "^0.16.3",
|
||||
"husky": "^8.0.2",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.0.0",
|
||||
"lint-staged": "^13.1.0",
|
||||
"postcss": "^8.4.20",
|
||||
"prettier": "2.8.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^3.7.4",
|
||||
"rollup-plugin-visualizer": "^5.8.2",
|
||||
"stylelint": "^14.16.0",
|
||||
"stylelint-config-prettier": "^9.0.4",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "^4.0.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-purge-icons": "^0.9.2",
|
||||
"vite-plugin-theme": "^0.8.1",
|
||||
"vite-plugin-windicss": "^1.8.10",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"windicss": "^3.5.6"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged"
|
||||
}
|
||||
},
|
||||
"gitHead": "9c2162697f3affea22c9a8cbc0ca74f4034ab27e",
|
||||
"vite": {
|
||||
"optimizeDeps": {
|
||||
"include": [
|
||||
"@iconify/iconify"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,7 @@
|
|||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="500" height="500" viewBox="0 0 500.000000 500.000000"
|
||||
>
|
||||
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
|
||||
transform="translate(70, 76) scale(6,6)"
|
||||
></path>
|
||||
</svg>
|
After Width: | Height: | Size: 402 B |
|
@ -0,0 +1,44 @@
|
|||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800" height="300" viewBox="0 0 800.000000 300.000000"
|
||||
>
|
||||
|
||||
<g fill="#333">
|
||||
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
|
||||
transform="translate(40, 40) scale(4,4)"
|
||||
></path>
|
||||
<g transform="translate(280, 260) scale(2,2)">
|
||||
<path d="M28.95-58.70L49.98-58.70L49.98-48.22L33.70-48.22Q27.37-48.22 24.46-47.34Q21.56-46.46 19.62-43.91L19.62-43.91Q18.30-42.15 17.82-40.39Q17.34-38.63 16.98-34.41L16.98-34.41L49.98-34.41L49.98-23.94L16.98-23.94Q17.69-16.37 21.47-13.42Q25.26-10.47 34.32-10.47L34.32-10.47L49.98-10.47L49.98 0L33.70 0Q27.10 0 22.09-0.79L22.09-0.79Q14.17-2.11 9.50-7.74L9.50-7.74Q2.82-15.84 2.82-29.66L2.82-29.66Q2.82-44.97 12.23-53.50L12.23-53.50Q15.49-56.41 19.14-57.55Q22.79-58.70 28.95-58.70L28.95-58.70Z"
|
||||
transform="translate(0 0) "
|
||||
|
||||
></path>
|
||||
<path d="M28.51-22.26L18.48-22.26L18.48 0L5.98 0L5.98-58.70L34.67-58.70Q46.99-58.70 51.83-55.53L51.83-55.53Q55.18-53.42 56.98-49.94Q58.78-46.46 58.78-42.33L58.78-42.33Q58.78-33.97 54.56-29.39L54.56-29.39Q51.92-26.66 47.61-25.26L47.61-25.26Q51.04-24.02 52.80-22.66Q54.56-21.30 55.97-18.66L55.97-18.66Q57.02-16.54 57.51-14.61Q57.99-12.67 58.26-8.89L58.26-8.89Q58.78-2.55 59.66 0L59.66 0L45.67 0Q45.06-2.02 44.35-8.36L44.35-8.36Q43.91-13.46 42.55-16.19Q41.18-18.92 38.46-20.50L38.46-20.50Q35.02-22.35 28.51-22.26L28.51-22.26ZM18.48-48.22L18.48-32.74L35.99-32.74Q40.39-32.74 42.24-34.06L42.24-34.06Q45.06-35.99 45.06-40.83L45.06-40.83Q45.06-46.20 40.83-47.70L40.83-47.70Q39.34-48.22 35.99-48.22L35.99-48.22L18.48-48.22Z"
|
||||
transform="translate(58.855999999999995 0) "
|
||||
|
||||
></path>
|
||||
<path d="M19.01 0L19.01-48.22L0.35-48.22L0.35-58.70L50.95-58.70L50.95-48.22L31.50-48.22L31.50 0L19.01 0Z"
|
||||
transform="translate(126.68799999999999 0) "
|
||||
></path>
|
||||
<path d="M5.98 0L5.98-58.70L30.89-58.70Q40.22-58.70 45.32-56.85L45.32-56.85Q54.74-53.42 57.90-44.26L57.90-44.26Q60.28-37.22 60.28-29.22L60.28-29.22Q60.28-21.30 57.99-14.26L57.99-14.26Q55.70-7.04 50.42-3.61L50.42-3.61Q47.08-1.50 43.08-0.75Q39.07 0 30.89 0L30.89 0L5.98 0ZM30.89-48.22L18.48-48.22L18.48-10.47L30.89-10.47Q39.07-10.47 42.24-14.08L42.24-14.08Q44.18-16.37 45.36-20.50Q46.55-24.64 46.55-29.30L46.55-29.30Q46.55-34.50 45.14-38.81Q43.74-43.12 41.45-45.23L41.45-45.23Q38.10-48.22 30.89-48.22L30.89-48.22Z"
|
||||
transform="translate(183.07999999999998 0) "
|
||||
></path>
|
||||
</g>
|
||||
<!-- <path d="M13.00-21.91L21.24-21.91L21.24-17.23L14.04-17.23Q10.39-17.23-->
|
||||
<!-- 9-15.62L9-15.62Q7.65-14.08-->
|
||||
<!-- 7.65-10.93L7.65-10.93Q7.65-7.42 9.86-5.80L9.86-5.80Q10.75-5.17-->
|
||||
<!-- 11.81-4.93Q12.87-4.68-->
|
||||
<!-- 14.76-4.68L14.76-4.68L21.24-4.68L21.24-->
|
||||
<!-- 0L13.00 0Q9.67 0 7.74-0.67Q5.80-1.35-->
|
||||
<!-- 4.32-3.01L4.32-3.01Q1.48-6.17-->
|
||||
<!-- 1.48-11.03L1.48-11.03Q1.48-16.88-->
|
||||
<!-- 4.86-19.75L4.86-19.75Q6.21-20.93-->
|
||||
<!-- 8.10-21.42Q9.99-21.91-->
|
||||
<!-- 13.00-21.91L13.00-21.91ZM31.05-13.32L43.74-13.32L43.74-8.64L31.05-8.64Q31.23-6.48-->
|
||||
<!-- 32.44-5.58Q33.66-4.68 36.41-4.68L36.41-4.68L43.74-4.68L43.74 0L35.73 0Q33.12 0 31.48-0.47Q29.83-0.94 28.39-2.02L28.39-2.02Q24.39-5.13 24.39-11.25L24.39-11.25Q24.39-15.21 26.50-18.18L26.50-18.18Q27.94-20.20 29.97-21.06Q31.99-21.91 35.28-21.91L35.28-21.91L43.74-21.91L43.74-17.23L35.73-17.23Q33.25-17.23 32.27-16.40Q31.27-15.57 31.05-13.32L31.05-13.32ZM48.64 0L48.64-21.91L57.55-21.91Q60.30-21.91 61.81-21.53Q63.31-21.15 64.31-20.25L64.31-20.25Q65.30-19.35 65.70-18Q66.10-16.65 66.10-14.13L66.10-14.13L66.10-12.01L60.30-12.01L60.30-13.18Q60.30-15.52 59.49-16.38Q58.68-17.23 56.38-17.23L56.38-17.23L54.67-17.23L54.67 0L48.64 0ZM67.50-21.91L71.33-21.91L71.33-30.02L77.36-30.02L77.36-21.91L82.94-21.91L82.94-17.23L77.36-17.23L77.36-9.27Q77.36-6.48 77.85-5.76L77.85-5.76Q78.61-4.68 80.55-4.68L80.55-4.68L82.94-4.68L82.94 0L78.57 0Q74.66 0 72.99-1.89Q71.33-3.78 71.33-8.23L71.33-8.23L71.33-17.23L67.50-17.23L67.50-21.91ZM96.08-21.91L101.75-21.91L101.75-30.02L107.73-30.02L107.73 0L97.38 0Q94.23 0 92.61-0.49L92.61-0.49Q88.78-1.71 86.90-5.26L86.90-5.26Q85.59-7.65 85.59-11.12L85.59-11.12Q85.59-16.74 89.37-19.84L89.37-19.84Q91.84-21.91 96.08-21.91L96.08-21.91ZM97.38-4.68L101.75-4.68L101.75-17.23L97.38-17.23Q94.50-17.23 93.02-15.30L93.02-15.30Q91.71-13.68 91.71-11.12L91.71-11.12Q91.71-7.38 93.87-5.76L93.87-5.76Q95.36-4.68 97.38-4.68L97.38-4.68Z"-->
|
||||
<!-- fill="#2c3e50"-->
|
||||
<!-- transform="translate(300, 270) scale(4,4)"-->
|
||||
|
||||
<!-- ></path>-->
|
||||
<text x="300" y="100" font-size="50" font-weight="bold">让你的证书永不过期</text>
|
||||
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
|
@ -0,0 +1,44 @@
|
|||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800" height="300" viewBox="0 0 800.000000 300.000000"
|
||||
>
|
||||
|
||||
<g fill="#fff">
|
||||
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
|
||||
transform="translate(40, 40) scale(4,4)"
|
||||
></path>
|
||||
<g transform="translate(280, 260) scale(2,2)">
|
||||
<path d="M28.95-58.70L49.98-58.70L49.98-48.22L33.70-48.22Q27.37-48.22 24.46-47.34Q21.56-46.46 19.62-43.91L19.62-43.91Q18.30-42.15 17.82-40.39Q17.34-38.63 16.98-34.41L16.98-34.41L49.98-34.41L49.98-23.94L16.98-23.94Q17.69-16.37 21.47-13.42Q25.26-10.47 34.32-10.47L34.32-10.47L49.98-10.47L49.98 0L33.70 0Q27.10 0 22.09-0.79L22.09-0.79Q14.17-2.11 9.50-7.74L9.50-7.74Q2.82-15.84 2.82-29.66L2.82-29.66Q2.82-44.97 12.23-53.50L12.23-53.50Q15.49-56.41 19.14-57.55Q22.79-58.70 28.95-58.70L28.95-58.70Z"
|
||||
transform="translate(0 0) "
|
||||
|
||||
></path>
|
||||
<path d="M28.51-22.26L18.48-22.26L18.48 0L5.98 0L5.98-58.70L34.67-58.70Q46.99-58.70 51.83-55.53L51.83-55.53Q55.18-53.42 56.98-49.94Q58.78-46.46 58.78-42.33L58.78-42.33Q58.78-33.97 54.56-29.39L54.56-29.39Q51.92-26.66 47.61-25.26L47.61-25.26Q51.04-24.02 52.80-22.66Q54.56-21.30 55.97-18.66L55.97-18.66Q57.02-16.54 57.51-14.61Q57.99-12.67 58.26-8.89L58.26-8.89Q58.78-2.55 59.66 0L59.66 0L45.67 0Q45.06-2.02 44.35-8.36L44.35-8.36Q43.91-13.46 42.55-16.19Q41.18-18.92 38.46-20.50L38.46-20.50Q35.02-22.35 28.51-22.26L28.51-22.26ZM18.48-48.22L18.48-32.74L35.99-32.74Q40.39-32.74 42.24-34.06L42.24-34.06Q45.06-35.99 45.06-40.83L45.06-40.83Q45.06-46.20 40.83-47.70L40.83-47.70Q39.34-48.22 35.99-48.22L35.99-48.22L18.48-48.22Z"
|
||||
transform="translate(58.855999999999995 0) "
|
||||
|
||||
></path>
|
||||
<path d="M19.01 0L19.01-48.22L0.35-48.22L0.35-58.70L50.95-58.70L50.95-48.22L31.50-48.22L31.50 0L19.01 0Z"
|
||||
transform="translate(126.68799999999999 0) "
|
||||
></path>
|
||||
<path d="M5.98 0L5.98-58.70L30.89-58.70Q40.22-58.70 45.32-56.85L45.32-56.85Q54.74-53.42 57.90-44.26L57.90-44.26Q60.28-37.22 60.28-29.22L60.28-29.22Q60.28-21.30 57.99-14.26L57.99-14.26Q55.70-7.04 50.42-3.61L50.42-3.61Q47.08-1.50 43.08-0.75Q39.07 0 30.89 0L30.89 0L5.98 0ZM30.89-48.22L18.48-48.22L18.48-10.47L30.89-10.47Q39.07-10.47 42.24-14.08L42.24-14.08Q44.18-16.37 45.36-20.50Q46.55-24.64 46.55-29.30L46.55-29.30Q46.55-34.50 45.14-38.81Q43.74-43.12 41.45-45.23L41.45-45.23Q38.10-48.22 30.89-48.22L30.89-48.22Z"
|
||||
transform="translate(183.07999999999998 0) "
|
||||
></path>
|
||||
</g>
|
||||
<!-- <path d="M13.00-21.91L21.24-21.91L21.24-17.23L14.04-17.23Q10.39-17.23-->
|
||||
<!-- 9-15.62L9-15.62Q7.65-14.08-->
|
||||
<!-- 7.65-10.93L7.65-10.93Q7.65-7.42 9.86-5.80L9.86-5.80Q10.75-5.17-->
|
||||
<!-- 11.81-4.93Q12.87-4.68-->
|
||||
<!-- 14.76-4.68L14.76-4.68L21.24-4.68L21.24-->
|
||||
<!-- 0L13.00 0Q9.67 0 7.74-0.67Q5.80-1.35-->
|
||||
<!-- 4.32-3.01L4.32-3.01Q1.48-6.17-->
|
||||
<!-- 1.48-11.03L1.48-11.03Q1.48-16.88-->
|
||||
<!-- 4.86-19.75L4.86-19.75Q6.21-20.93-->
|
||||
<!-- 8.10-21.42Q9.99-21.91-->
|
||||
<!-- 13.00-21.91L13.00-21.91ZM31.05-13.32L43.74-13.32L43.74-8.64L31.05-8.64Q31.23-6.48-->
|
||||
<!-- 32.44-5.58Q33.66-4.68 36.41-4.68L36.41-4.68L43.74-4.68L43.74 0L35.73 0Q33.12 0 31.48-0.47Q29.83-0.94 28.39-2.02L28.39-2.02Q24.39-5.13 24.39-11.25L24.39-11.25Q24.39-15.21 26.50-18.18L26.50-18.18Q27.94-20.20 29.97-21.06Q31.99-21.91 35.28-21.91L35.28-21.91L43.74-21.91L43.74-17.23L35.73-17.23Q33.25-17.23 32.27-16.40Q31.27-15.57 31.05-13.32L31.05-13.32ZM48.64 0L48.64-21.91L57.55-21.91Q60.30-21.91 61.81-21.53Q63.31-21.15 64.31-20.25L64.31-20.25Q65.30-19.35 65.70-18Q66.10-16.65 66.10-14.13L66.10-14.13L66.10-12.01L60.30-12.01L60.30-13.18Q60.30-15.52 59.49-16.38Q58.68-17.23 56.38-17.23L56.38-17.23L54.67-17.23L54.67 0L48.64 0ZM67.50-21.91L71.33-21.91L71.33-30.02L77.36-30.02L77.36-21.91L82.94-21.91L82.94-17.23L77.36-17.23L77.36-9.27Q77.36-6.48 77.85-5.76L77.85-5.76Q78.61-4.68 80.55-4.68L80.55-4.68L82.94-4.68L82.94 0L78.57 0Q74.66 0 72.99-1.89Q71.33-3.78 71.33-8.23L71.33-8.23L71.33-17.23L67.50-17.23L67.50-21.91ZM96.08-21.91L101.75-21.91L101.75-30.02L107.73-30.02L107.73 0L97.38 0Q94.23 0 92.61-0.49L92.61-0.49Q88.78-1.71 86.90-5.26L86.90-5.26Q85.59-7.65 85.59-11.12L85.59-11.12Q85.59-16.74 89.37-19.84L89.37-19.84Q91.84-21.91 96.08-21.91L96.08-21.91ZM97.38-4.68L101.75-4.68L101.75-17.23L97.38-17.23Q94.50-17.23 93.02-15.30L93.02-15.30Q91.71-13.68 91.71-11.12L91.71-11.12Q91.71-7.38 93.87-5.76L93.87-5.76Q95.36-4.68 97.38-4.68L97.38-4.68Z"-->
|
||||
<!-- fill="#2c3e50"-->
|
||||
<!-- transform="translate(300, 270) scale(4,4)"-->
|
||||
|
||||
<!-- ></path>-->
|
||||
<text x="300" y="100" font-size="50" font-weight="bold">让你的证书永不过期</text>
|
||||
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
|
@ -0,0 +1,17 @@
|
|||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="500" height="500" viewBox="0 0 500.000000 500.000000"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
|
||||
fill="#2c3e50"
|
||||
transform="translate(124, 60) scale(4,4)"
|
||||
></path>
|
||||
<path d="M13.00-21.91L21.24-21.91L21.24-17.23L14.04-17.23Q10.39-17.23 9-15.62L9-15.62Q7.65-14.08 7.65-10.93L7.65-10.93Q7.65-7.42 9.86-5.80L9.86-5.80Q10.75-5.17 11.81-4.93Q12.87-4.68 14.76-4.68L14.76-4.68L21.24-4.68L21.24 0L13.00 0Q9.67 0 7.74-0.67Q5.80-1.35 4.32-3.01L4.32-3.01Q1.48-6.17 1.48-11.03L1.48-11.03Q1.48-16.88 4.86-19.75L4.86-19.75Q6.21-20.93 8.10-21.42Q9.99-21.91 13.00-21.91L13.00-21.91ZM31.05-13.32L43.74-13.32L43.74-8.64L31.05-8.64Q31.23-6.48 32.44-5.58Q33.66-4.68 36.41-4.68L36.41-4.68L43.74-4.68L43.74 0L35.73 0Q33.12 0 31.48-0.47Q29.83-0.94 28.39-2.02L28.39-2.02Q24.39-5.13 24.39-11.25L24.39-11.25Q24.39-15.21 26.50-18.18L26.50-18.18Q27.94-20.20 29.97-21.06Q31.99-21.91 35.28-21.91L35.28-21.91L43.74-21.91L43.74-17.23L35.73-17.23Q33.25-17.23 32.27-16.40Q31.27-15.57 31.05-13.32L31.05-13.32ZM48.64 0L48.64-21.91L57.55-21.91Q60.30-21.91 61.81-21.53Q63.31-21.15 64.31-20.25L64.31-20.25Q65.30-19.35 65.70-18Q66.10-16.65 66.10-14.13L66.10-14.13L66.10-12.01L60.30-12.01L60.30-13.18Q60.30-15.52 59.49-16.38Q58.68-17.23 56.38-17.23L56.38-17.23L54.67-17.23L54.67 0L48.64 0ZM67.50-21.91L71.33-21.91L71.33-30.02L77.36-30.02L77.36-21.91L82.94-21.91L82.94-17.23L77.36-17.23L77.36-9.27Q77.36-6.48 77.85-5.76L77.85-5.76Q78.61-4.68 80.55-4.68L80.55-4.68L82.94-4.68L82.94 0L78.57 0Q74.66 0 72.99-1.89Q71.33-3.78 71.33-8.23L71.33-8.23L71.33-17.23L67.50-17.23L67.50-21.91ZM96.08-21.91L101.75-21.91L101.75-30.02L107.73-30.02L107.73 0L97.38 0Q94.23 0 92.61-0.49L92.61-0.49Q88.78-1.71 86.90-5.26L86.90-5.26Q85.59-7.65 85.59-11.12L85.59-11.12Q85.59-16.74 89.37-19.84L89.37-19.84Q91.84-21.91 96.08-21.91L96.08-21.91ZM97.38-4.68L101.75-4.68L101.75-17.23L97.38-17.23Q94.50-17.23 93.02-15.30L93.02-15.30Q91.71-13.68 91.71-11.12L91.71-11.12Q91.71-7.38 93.87-5.76L93.87-5.76Q95.36-4.68 97.38-4.68L97.38-4.68Z"
|
||||
fill="#2c3e50"
|
||||
transform="translate(28, 430) scale(4,4)"
|
||||
|
||||
></path>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,7 @@
|
|||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="500" height="500" viewBox="0 0 500.000000 500.000000"
|
||||
>
|
||||
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
|
||||
transform="translate(70, 76) scale(6,6)"
|
||||
></path>
|
||||
</svg>
|
After Width: | Height: | Size: 402 B |
|
@ -0,0 +1,108 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="210mm"
|
||||
height="210mm"
|
||||
viewBox="0 0 210 210"
|
||||
version="1.1"
|
||||
id="svg8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
id="layer1"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:#002255;stroke:none;stroke-width:0.625348"
|
||||
d="M 35.587501,69.766667 V 59.766664 h 70.000109 69.99991 v 10.000003 9.999997 H 105.58761 35.587501 Z"
|
||||
id="path12" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-3"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4-0"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-9"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-37"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7-1"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="82.941666" />
|
||||
</g>
|
||||
<polygon
|
||||
points="75.3,174.4 103.1,103.6 79.8,103.6 112.6,41.3 156.4,41.3 129.9,90.5 148.1,90.5 "
|
||||
fill="#f6cc00"
|
||||
id="polygon276"
|
||||
transform="matrix(1.0930933,0,0,0.99853202,-17.517362,-0.52287941)" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,44 @@
|
|||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800" height="300" viewBox="0 0 800.000000 300.000000"
|
||||
>
|
||||
|
||||
<g fill="#fff">
|
||||
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
|
||||
transform="translate(40, 40) scale(4,4)"
|
||||
></path>
|
||||
<g transform="translate(280, 260) scale(2,2)">
|
||||
<path d="M28.95-58.70L49.98-58.70L49.98-48.22L33.70-48.22Q27.37-48.22 24.46-47.34Q21.56-46.46 19.62-43.91L19.62-43.91Q18.30-42.15 17.82-40.39Q17.34-38.63 16.98-34.41L16.98-34.41L49.98-34.41L49.98-23.94L16.98-23.94Q17.69-16.37 21.47-13.42Q25.26-10.47 34.32-10.47L34.32-10.47L49.98-10.47L49.98 0L33.70 0Q27.10 0 22.09-0.79L22.09-0.79Q14.17-2.11 9.50-7.74L9.50-7.74Q2.82-15.84 2.82-29.66L2.82-29.66Q2.82-44.97 12.23-53.50L12.23-53.50Q15.49-56.41 19.14-57.55Q22.79-58.70 28.95-58.70L28.95-58.70Z"
|
||||
transform="translate(0 0) "
|
||||
|
||||
></path>
|
||||
<path d="M28.51-22.26L18.48-22.26L18.48 0L5.98 0L5.98-58.70L34.67-58.70Q46.99-58.70 51.83-55.53L51.83-55.53Q55.18-53.42 56.98-49.94Q58.78-46.46 58.78-42.33L58.78-42.33Q58.78-33.97 54.56-29.39L54.56-29.39Q51.92-26.66 47.61-25.26L47.61-25.26Q51.04-24.02 52.80-22.66Q54.56-21.30 55.97-18.66L55.97-18.66Q57.02-16.54 57.51-14.61Q57.99-12.67 58.26-8.89L58.26-8.89Q58.78-2.55 59.66 0L59.66 0L45.67 0Q45.06-2.02 44.35-8.36L44.35-8.36Q43.91-13.46 42.55-16.19Q41.18-18.92 38.46-20.50L38.46-20.50Q35.02-22.35 28.51-22.26L28.51-22.26ZM18.48-48.22L18.48-32.74L35.99-32.74Q40.39-32.74 42.24-34.06L42.24-34.06Q45.06-35.99 45.06-40.83L45.06-40.83Q45.06-46.20 40.83-47.70L40.83-47.70Q39.34-48.22 35.99-48.22L35.99-48.22L18.48-48.22Z"
|
||||
transform="translate(58.855999999999995 0) "
|
||||
|
||||
></path>
|
||||
<path d="M19.01 0L19.01-48.22L0.35-48.22L0.35-58.70L50.95-58.70L50.95-48.22L31.50-48.22L31.50 0L19.01 0Z"
|
||||
transform="translate(126.68799999999999 0) "
|
||||
></path>
|
||||
<path d="M5.98 0L5.98-58.70L30.89-58.70Q40.22-58.70 45.32-56.85L45.32-56.85Q54.74-53.42 57.90-44.26L57.90-44.26Q60.28-37.22 60.28-29.22L60.28-29.22Q60.28-21.30 57.99-14.26L57.99-14.26Q55.70-7.04 50.42-3.61L50.42-3.61Q47.08-1.50 43.08-0.75Q39.07 0 30.89 0L30.89 0L5.98 0ZM30.89-48.22L18.48-48.22L18.48-10.47L30.89-10.47Q39.07-10.47 42.24-14.08L42.24-14.08Q44.18-16.37 45.36-20.50Q46.55-24.64 46.55-29.30L46.55-29.30Q46.55-34.50 45.14-38.81Q43.74-43.12 41.45-45.23L41.45-45.23Q38.10-48.22 30.89-48.22L30.89-48.22Z"
|
||||
transform="translate(183.07999999999998 0) "
|
||||
></path>
|
||||
</g>
|
||||
<!-- <path d="M13.00-21.91L21.24-21.91L21.24-17.23L14.04-17.23Q10.39-17.23-->
|
||||
<!-- 9-15.62L9-15.62Q7.65-14.08-->
|
||||
<!-- 7.65-10.93L7.65-10.93Q7.65-7.42 9.86-5.80L9.86-5.80Q10.75-5.17-->
|
||||
<!-- 11.81-4.93Q12.87-4.68-->
|
||||
<!-- 14.76-4.68L14.76-4.68L21.24-4.68L21.24-->
|
||||
<!-- 0L13.00 0Q9.67 0 7.74-0.67Q5.80-1.35-->
|
||||
<!-- 4.32-3.01L4.32-3.01Q1.48-6.17-->
|
||||
<!-- 1.48-11.03L1.48-11.03Q1.48-16.88-->
|
||||
<!-- 4.86-19.75L4.86-19.75Q6.21-20.93-->
|
||||
<!-- 8.10-21.42Q9.99-21.91-->
|
||||
<!-- 13.00-21.91L13.00-21.91ZM31.05-13.32L43.74-13.32L43.74-8.64L31.05-8.64Q31.23-6.48-->
|
||||
<!-- 32.44-5.58Q33.66-4.68 36.41-4.68L36.41-4.68L43.74-4.68L43.74 0L35.73 0Q33.12 0 31.48-0.47Q29.83-0.94 28.39-2.02L28.39-2.02Q24.39-5.13 24.39-11.25L24.39-11.25Q24.39-15.21 26.50-18.18L26.50-18.18Q27.94-20.20 29.97-21.06Q31.99-21.91 35.28-21.91L35.28-21.91L43.74-21.91L43.74-17.23L35.73-17.23Q33.25-17.23 32.27-16.40Q31.27-15.57 31.05-13.32L31.05-13.32ZM48.64 0L48.64-21.91L57.55-21.91Q60.30-21.91 61.81-21.53Q63.31-21.15 64.31-20.25L64.31-20.25Q65.30-19.35 65.70-18Q66.10-16.65 66.10-14.13L66.10-14.13L66.10-12.01L60.30-12.01L60.30-13.18Q60.30-15.52 59.49-16.38Q58.68-17.23 56.38-17.23L56.38-17.23L54.67-17.23L54.67 0L48.64 0ZM67.50-21.91L71.33-21.91L71.33-30.02L77.36-30.02L77.36-21.91L82.94-21.91L82.94-17.23L77.36-17.23L77.36-9.27Q77.36-6.48 77.85-5.76L77.85-5.76Q78.61-4.68 80.55-4.68L80.55-4.68L82.94-4.68L82.94 0L78.57 0Q74.66 0 72.99-1.89Q71.33-3.78 71.33-8.23L71.33-8.23L71.33-17.23L67.50-17.23L67.50-21.91ZM96.08-21.91L101.75-21.91L101.75-30.02L107.73-30.02L107.73 0L97.38 0Q94.23 0 92.61-0.49L92.61-0.49Q88.78-1.71 86.90-5.26L86.90-5.26Q85.59-7.65 85.59-11.12L85.59-11.12Q85.59-16.74 89.37-19.84L89.37-19.84Q91.84-21.91 96.08-21.91L96.08-21.91ZM97.38-4.68L101.75-4.68L101.75-17.23L97.38-17.23Q94.50-17.23 93.02-15.30L93.02-15.30Q91.71-13.68 91.71-11.12L91.71-11.12Q91.71-7.38 93.87-5.76L93.87-5.76Q95.36-4.68 97.38-4.68L97.38-4.68Z"-->
|
||||
<!-- fill="#2c3e50"-->
|
||||
<!-- transform="translate(300, 270) scale(4,4)"-->
|
||||
|
||||
<!-- ></path>-->
|
||||
<text x="300" y="100" font-size="50" font-weight="bold">让你的证书永不过期</text>
|
||||
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
|
@ -0,0 +1,106 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="210mm"
|
||||
height="210mm"
|
||||
viewBox="0 0 210 210"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
>
|
||||
<g id="layer1" style="display:inline">
|
||||
<path
|
||||
style="fill:#002255;stroke:none;stroke-width:0.625348"
|
||||
d="M 35.587501,69.766667 V 59.766664 h 70.000109 69.99991 v 10.000003 9.999997 H 105.58761 35.587501 Z"
|
||||
id="path12" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-3"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4-0"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-9"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-37"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7-1"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="82.941666" />
|
||||
</g>
|
||||
<polygon
|
||||
points="75.3,174.4 103.1,103.6 79.8,103.6 112.6,41.3 156.4,41.3 129.9,90.5 148.1,90.5 "
|
||||
fill="#f6cc00"
|
||||
id="polygon276"
|
||||
transform="matrix(1.0930933,0,0,0.99853202,-17.517362,-0.52287941)" />
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 7.6 KiB |
|
@ -0,0 +1,12 @@
|
|||
html, body, #app { height: 100%; margin: 0; padding: 0; width: 100%;}
|
||||
.fs-bootstrap { background-color: #474949; height: 100%; display: flex; flex-direction: column;position: fixed;width: 100% }
|
||||
.fs-bootstrap__main {flex:1; user-select: none; width: 100%; flex-grow: 1; display: flex; justify-content: center; align-items: center; flex-direction: column; }
|
||||
.fs-bootstrap__footer { width: 100%; flex-grow: 0; text-align: center; padding: 10px 0; }
|
||||
.fs-bootstrap__footer > a { font-size: 12px; color: #ABABAB; text-decoration: none; }
|
||||
.fs-bootstrap__loading {box-sizing: border-box; height: 50px; width: 50px; margin-bottom: 5px;border:5px solid #333333;border-bottom:#aaa 5px solid;
|
||||
border-radius:1000px; animation:load 1.1s infinite linear;-webkit-animation:load 1.1s infinite linear;-moz-animation:load 1.1s infinite linear; -o-animation:load 1.1s infinite linear;
|
||||
}
|
||||
@keyframes load {from {transform:rotate(0deg);-ms-transform:rotate(0deg);}to { transform:rotate(360deg);-ms-transform:rotate(360deg); }
|
||||
}@-webkit-keyframes load {from {-webkit-transform:rotate(0deg); }to { -webkit-transform:rotate(360deg);}
|
||||
}@-moz-keyframes load { from { -moz-transform:rotate(0deg); } to { -moz-transform:rotate(360deg);}
|
||||
}@-o-keyframes load { from { -o-transform:rotate(0deg);} to { -o-transform:rotate(360deg);}}
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,106 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="210mm"
|
||||
height="210mm"
|
||||
viewBox="0 0 210 210"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
>
|
||||
<g id="layer1" style="display:inline">
|
||||
<path
|
||||
style="fill:#002255;stroke:none;stroke-width:0.625348"
|
||||
d="M 35.587501,69.766667 V 59.766664 h 70.000109 69.99991 v 10.000003 9.999997 H 105.58761 35.587501 Z"
|
||||
id="path12" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-3"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-2"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="106.64581" />
|
||||
<rect
|
||||
style="fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-4-0"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="129.82079" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-9"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="71.506088"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-8-37"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="107.42467"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-8-5-8-4"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="143.34325"
|
||||
y="82.941666" />
|
||||
<rect
|
||||
style="display:inline;fill:#2a7fff;fill-rule:evenodd;stroke-width:0.214311"
|
||||
id="rect22-2-7-1"
|
||||
width="32.244232"
|
||||
height="20"
|
||||
x="35.587502"
|
||||
y="82.941666" />
|
||||
</g>
|
||||
<polygon
|
||||
points="75.3,174.4 103.1,103.6 79.8,103.6 112.6,41.3 156.4,41.3 129.9,90.5 148.1,90.5 "
|
||||
fill="#f6cc00"
|
||||
id="polygon276"
|
||||
transform="matrix(1.0930933,0,0,0.99853202,-17.517362,-0.52287941)" />
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<a-config-provider :locale="locale">
|
||||
<router-view v-if="routerEnabled" />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import zhCN from "ant-design-vue/es/locale/zh_CN";
|
||||
import enUS from "ant-design-vue/es/locale/en_US";
|
||||
import { provide, ref, nextTick } from "vue";
|
||||
import { usePageStore } from "/src/store/modules/page";
|
||||
import { useResourceStore } from "/src/store/modules/resource";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import 'dayjs/locale/en';
|
||||
import dayjs from 'dayjs'
|
||||
export default {
|
||||
name: "App",
|
||||
setup() {
|
||||
//刷新页面方法
|
||||
const routerEnabled = ref(true);
|
||||
const locale = ref(zhCN);
|
||||
async function reload() {
|
||||
routerEnabled.value = false;
|
||||
await nextTick();
|
||||
routerEnabled.value = true;
|
||||
}
|
||||
function localeChanged(value) {
|
||||
console.log("locale changed:", value);
|
||||
if (value === "zh-cn") {
|
||||
locale.value = zhCN;
|
||||
dayjs.locale('zh-cn');
|
||||
} else if (value === "en") {
|
||||
locale.value = enUS;
|
||||
dayjs.locale('en');
|
||||
}
|
||||
}
|
||||
localeChanged('zh-cn')
|
||||
provide("fn:router.reload", reload);
|
||||
provide("fn:locale.changed", localeChanged);
|
||||
|
||||
//其他初始化
|
||||
const resourceStore = useResourceStore();
|
||||
resourceStore.init();
|
||||
const pageStore = usePageStore();
|
||||
pageStore.init();
|
||||
const settingStore = useSettingStore();
|
||||
settingStore.init();
|
||||
|
||||
return {
|
||||
routerEnabled,
|
||||
locale
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,31 @@
|
|||
export default [
|
||||
{
|
||||
path: "/login",
|
||||
method: "post",
|
||||
handle() {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: {
|
||||
token: "faker token",
|
||||
expire: 10000
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/sys/authority/user/mine",
|
||||
method: "get",
|
||||
handle() {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: {
|
||||
id: 1,
|
||||
username: "username",
|
||||
nickName: "admin"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
|
@ -0,0 +1,51 @@
|
|||
import { request, requestForMock } from "../service";
|
||||
import { env } from "/@/utils/util.env";
|
||||
/**
|
||||
* @description: Login interface parameters
|
||||
*/
|
||||
export interface LoginReq {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface UserInfoRes {
|
||||
id: string | number;
|
||||
username: string;
|
||||
nickName: string;
|
||||
}
|
||||
|
||||
export interface LoginRes {
|
||||
token: string;
|
||||
expire: number;
|
||||
}
|
||||
|
||||
export async function login(data: LoginReq): Promise<LoginRes> {
|
||||
if (env.PM_ENABLED === "false") {
|
||||
//没有开启权限模块,模拟登录
|
||||
return await requestForMock({
|
||||
url: "/login",
|
||||
method: "post",
|
||||
data
|
||||
});
|
||||
}
|
||||
//如果开启了登录与权限模块,则真实登录
|
||||
return await request({
|
||||
url: "/login",
|
||||
method: "post",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export async function mine(): Promise<UserInfoRes> {
|
||||
if (env.PM_ENABLED === "false") {
|
||||
//没有开启权限模块,模拟登录
|
||||
return await requestForMock({
|
||||
url: "/sys/authority/user/mine",
|
||||
method: "post"
|
||||
});
|
||||
}
|
||||
return await request({
|
||||
url: "/sys/authority/user/mine",
|
||||
method: "post"
|
||||
});
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
import axios from "axios";
|
||||
import { get } from "lodash-es";
|
||||
import Adapter from "axios-mock-adapter";
|
||||
import { errorLog, errorCreate } from "./tools";
|
||||
import { env } from "/src/utils/util.env";
|
||||
import { useUserStore } from "../store/modules/user";
|
||||
/**
|
||||
* @description 创建请求实例
|
||||
*/
|
||||
function createService() {
|
||||
// 创建一个 axios 实例
|
||||
const service = axios.create();
|
||||
// 请求拦截
|
||||
service.interceptors.request.use(
|
||||
(config) => config,
|
||||
(error) => {
|
||||
// 发送失败
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
// 响应拦截
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
if (response.config.responseType === "blob") {
|
||||
return response;
|
||||
}
|
||||
// dataAxios 是 axios 返回数据中的 data
|
||||
const dataAxios = response.data;
|
||||
// 这个状态码是和后端约定的
|
||||
const { code } = dataAxios;
|
||||
// 根据 code 进行判断
|
||||
if (code === undefined) {
|
||||
// 如果没有 code 代表这不是项目后端开发的接口 比如可能是 D2Admin 请求最新版本
|
||||
errorCreate(`非标准返回:${dataAxios}, ${response.config.url}`);
|
||||
return dataAxios;
|
||||
} else {
|
||||
// 有 code 代表这是一个后端接口 可以进行进一步的判断
|
||||
switch (code) {
|
||||
case 0:
|
||||
// [ 示例 ] code === 0 代表没有错误
|
||||
// @ts-ignore
|
||||
if (response.config.unpack === false) {
|
||||
//如果不需要解包
|
||||
return dataAxios;
|
||||
}
|
||||
return dataAxios.data;
|
||||
default:
|
||||
// 不是正确的 code
|
||||
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
|
||||
return dataAxios;
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
const status = get(error, "response.status");
|
||||
switch (status) {
|
||||
case 400:
|
||||
error.message = "请求错误";
|
||||
break;
|
||||
case 401:
|
||||
error.message = "未授权,请登录";
|
||||
break;
|
||||
case 403:
|
||||
error.message = "拒绝访问";
|
||||
break;
|
||||
case 404:
|
||||
error.message = `请求地址出错: ${error.response.config.url}`;
|
||||
break;
|
||||
case 408:
|
||||
error.message = "请求超时";
|
||||
break;
|
||||
case 500:
|
||||
error.message = "服务器内部错误";
|
||||
break;
|
||||
case 501:
|
||||
error.message = "服务未实现";
|
||||
break;
|
||||
case 502:
|
||||
error.message = "网关错误";
|
||||
break;
|
||||
case 503:
|
||||
error.message = "服务不可用";
|
||||
break;
|
||||
case 504:
|
||||
error.message = "网关超时";
|
||||
break;
|
||||
case 505:
|
||||
error.message = "HTTP版本不受支持";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
errorLog(error);
|
||||
if (status === 401) {
|
||||
const userStore = useUserStore();
|
||||
userStore.logout();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建请求方法
|
||||
* @param {Object} service axios 实例
|
||||
*/
|
||||
function createRequestFunction(service) {
|
||||
return function (config) {
|
||||
const configDefault = {
|
||||
headers: {
|
||||
"Content-Type": get(config, "headers.Content-Type", "application/json")
|
||||
},
|
||||
timeout: 5000,
|
||||
baseURL: env.API,
|
||||
data: {}
|
||||
};
|
||||
const userStore = useUserStore();
|
||||
const token = userStore.getToken;
|
||||
if (token != null) {
|
||||
// @ts-ignore
|
||||
configDefault.headers.Authorization = token;
|
||||
}
|
||||
return service(Object.assign(configDefault, config));
|
||||
};
|
||||
}
|
||||
|
||||
// 用于真实网络请求的实例和请求方法
|
||||
export const service = createService();
|
||||
export const request = createRequestFunction(service);
|
||||
|
||||
// 用于模拟网络请求的实例和请求方法
|
||||
export const serviceForMock = createService();
|
||||
export const requestForMock = createRequestFunction(serviceForMock);
|
||||
|
||||
// 网络请求数据模拟工具
|
||||
export const mock = new Adapter(serviceForMock, { delayResponse: 200 });
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @description 安全地解析 json 字符串
|
||||
* @param {String} jsonString 需要解析的 json 字符串
|
||||
* @param {String} defaultValue 默认值
|
||||
*/
|
||||
import { uiContext } from "@fast-crud/fast-crud";
|
||||
|
||||
export function parse(jsonString = "{}", defaultValue = {}) {
|
||||
let result = defaultValue;
|
||||
try {
|
||||
result = JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 接口请求返回
|
||||
* @param {Any} data 返回值
|
||||
* @param {String} msg 状态信息
|
||||
* @param {Number} code 状态码
|
||||
*/
|
||||
export function response(data = {}, msg = "", code = 0) {
|
||||
return [200, { code, msg, data }];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 接口请求返回 正确返回
|
||||
* @param {Any} data 返回值
|
||||
* @param {String} msg 状态信息
|
||||
*/
|
||||
export function responseSuccess(data = {}, msg = "成功") {
|
||||
return response(data, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 接口请求返回 错误返回
|
||||
* @param {Any} data 返回值
|
||||
* @param {String} msg 状态信息
|
||||
* @param {Number} code 状态码
|
||||
*/
|
||||
export function responseError(data = {}, msg = "请求失败", code = 500) {
|
||||
return response(data, msg, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 记录和显示错误
|
||||
* @param {Error} error 错误对象
|
||||
*/
|
||||
export function errorLog(error) {
|
||||
// 打印到控制台
|
||||
console.error(error);
|
||||
// 显示提示
|
||||
uiContext.get().notification.error({ message: error.message });
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建一个错误
|
||||
* @param {String} msg 错误信息
|
||||
*/
|
||||
export function errorCreate(msg) {
|
||||
const error = new Error(msg);
|
||||
errorLog(error);
|
||||
throw error;
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 21</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
|
||||
<g id="Group-21" transform="translate(77.000000, 73.000000)">
|
||||
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
|
||||
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
|
||||
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
|
||||
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
|
||||
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
|
||||
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
|
||||
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
|
||||
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
|
||||
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
|
||||
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
|
||||
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
|
||||
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
|
||||
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
|
||||
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
|
||||
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
|
||||
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
|
||||
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
|
||||
</g>
|
||||
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
|
||||
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
|
||||
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
|
||||
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
|
||||
</g>
|
||||
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
|
||||
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
|
||||
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
|
||||
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
|
||||
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
|
||||
</g>
|
||||
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
|
||||
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
|
||||
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
|
||||
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
|
||||
</g>
|
||||
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
|
||||
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
|
||||
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
|
||||
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
|
||||
</g>
|
||||
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
|
||||
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
|
||||
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
|
||||
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
|
||||
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
|
||||
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
|
||||
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
|
||||
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
|
||||
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<div class="pi-container">
|
||||
<div class="box">
|
||||
<div class="inner">
|
||||
<div class="header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div class="body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "PiContainer"
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.pi-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.box {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.inner {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.header {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.body {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,10 @@
|
|||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/pi/dnsProvider";
|
||||
|
||||
export async function GetList() {
|
||||
return await request({
|
||||
url: apiPrefix + "/list",
|
||||
method: "post"
|
||||
});
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<a-select class="pi-dns-provider-selector" :value="modelValue" :options="options" @update:value="onChanged">
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { inject, Ref, ref, watch } from "vue";
|
||||
import * as api from "./api";
|
||||
|
||||
export default {
|
||||
name: "PiDnsProviderSelector",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(props, ctx) {
|
||||
const options = ref<any[]>([]);
|
||||
|
||||
async function onCreate() {
|
||||
const list = await api.GetList();
|
||||
const array: any[] = [];
|
||||
for (let item of list) {
|
||||
array.push({
|
||||
value: item.name,
|
||||
label: item.title
|
||||
});
|
||||
}
|
||||
options.value = array;
|
||||
if (props.modelValue == null && options.value.length > 0) {
|
||||
ctx.emit("update:modelValue", options.value[0].value);
|
||||
}
|
||||
}
|
||||
onCreate();
|
||||
|
||||
function onChanged(value) {
|
||||
ctx.emit("update:modelValue", value);
|
||||
}
|
||||
return {
|
||||
options,
|
||||
onChanged
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.step-edit-form {
|
||||
.body {
|
||||
padding: 10px;
|
||||
.ant-card {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.current {
|
||||
border-color: #00b7ff;
|
||||
}
|
||||
|
||||
.ant-card-meta-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ant-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
padding: 14px;
|
||||
height: 100px;
|
||||
|
||||
overflow-y: hidden;
|
||||
|
||||
.ant-card-meta-description {
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
height: 40px;
|
||||
color: #7f7f7f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<div class="pi-editable" :class="{ disabled, 'hover-show': hoverShow }">
|
||||
<div v-if="isEdit" class="input">
|
||||
<a-input
|
||||
ref="inputRef"
|
||||
v-model:value="valueRef"
|
||||
:validate-status="modelValue ? '' : 'error'"
|
||||
v-bind="input"
|
||||
@keyup.enter="save()"
|
||||
@blur="save()"
|
||||
>
|
||||
<template #suffix>
|
||||
<fs-icon icon="ant-design:check-outlined" @click="save()"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
<div v-else class="view" @click="edit">
|
||||
<span> {{ modelValue }}</span>
|
||||
<fs-icon class="edit-icon" icon="ant-design:edit-outlined"></fs-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { watch, ref, nextTick } from "vue";
|
||||
|
||||
export default {
|
||||
name: "PiEditable",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
input: {
|
||||
type: Object
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hoverShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(props, ctx) {
|
||||
const inputRef = ref();
|
||||
const valueRef = ref(props.modelValue);
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
(value) => {
|
||||
valueRef.value = value;
|
||||
}
|
||||
);
|
||||
const isEdit = ref(false);
|
||||
async function edit() {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
isEdit.value = true;
|
||||
await nextTick();
|
||||
inputRef.value.focus();
|
||||
}
|
||||
function save() {
|
||||
isEdit.value = false;
|
||||
ctx.emit("update:modelValue", valueRef.value);
|
||||
}
|
||||
return {
|
||||
valueRef,
|
||||
isEdit,
|
||||
save,
|
||||
edit,
|
||||
inputRef
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.pi-editable {
|
||||
line-height: 34px;
|
||||
|
||||
span.fs-iconify {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.edit-icon {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
}
|
||||
&.hover-show {
|
||||
.edit-icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover {
|
||||
.edit-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
.edit-icon {
|
||||
line-height: 34px;
|
||||
}
|
||||
.view {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,18 @@
|
|||
import PiContainer from "./container.vue";
|
||||
import PiAccessSelector from "../views/certd/access/access-selector/index.vue";
|
||||
import PiDnsProviderSelector from "./dns-provider-selector/index.vue";
|
||||
import PiOutputSelector from "../views/certd/pipeline/pipeline/component/output-selector/index.vue";import PiEditable from "./editable.vue";
|
||||
import { CheckCircleOutlined, InfoCircleOutlined, UndoOutlined } from "@ant-design/icons-vue";
|
||||
export default {
|
||||
install(app) {
|
||||
app.component("PiContainer", PiContainer);
|
||||
app.component("PiAccessSelector", PiAccessSelector);
|
||||
app.component("PiEditable", PiEditable);
|
||||
app.component("PiOutputSelector", PiOutputSelector);
|
||||
app.component("PiDnsProviderSelector", PiDnsProviderSelector);
|
||||
|
||||
app.component("CheckCircleOutlined", CheckCircleOutlined);
|
||||
app.component("InfoCircleOutlined", InfoCircleOutlined);
|
||||
app.component("UndoOutlined", UndoOutlined);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
// @ts-ignore
|
||||
import { createI18n } from "vue-i18n";
|
||||
//
|
||||
// @ts-ignore
|
||||
import enFsLocale from "@fast-crud/fast-crud/dist/locale/lang/en.js";
|
||||
// @ts-ignore
|
||||
import zhFsLocale from "@fast-crud/fast-crud/dist/locale/lang/zh-cn.js";
|
||||
import en from "./locale/en";
|
||||
import zh from "./locale/zh_CN";
|
||||
const messages = {
|
||||
en: {
|
||||
label: "English",
|
||||
// 定义您自己的字典,但是请不要和 `fs` 重复,这样会导致 fast-crud 内部组件的翻译失效.
|
||||
fs: enFsLocale.fs,
|
||||
...en
|
||||
},
|
||||
"zh-cn": {
|
||||
label: "简体中文",
|
||||
// 定义您自己的字典,但是请不要和 `fs` 重复,这样会导致 fast-crud 内部组件的翻译失效.
|
||||
fs: zhFsLocale.fs,
|
||||
...zh
|
||||
}
|
||||
};
|
||||
|
||||
export default createI18n({
|
||||
legacy: false,
|
||||
locale: "zh-cn",
|
||||
fallbackLocale: "zh-cn",
|
||||
messages
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
app: { crud: { i18n: { name: "name", city: "city", status: "status" } } }
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
export default {
|
||||
app: {
|
||||
crud: { i18n: { name: "姓名", city: "城市", status: "状态" } },
|
||||
login: {
|
||||
logoutTip: "确认",
|
||||
logoutMessage: "确定要注销登录吗?"
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div class="fs-contentmenu-list" @click="rowClick">
|
||||
<div
|
||||
v-for="item in menulist"
|
||||
:key="item.value"
|
||||
:data-value="item.value"
|
||||
class="fs-contentmenu-item"
|
||||
flex="cross:center main:center"
|
||||
>
|
||||
<d2-icon v-if="item.icon" :name="item.icon" />
|
||||
<div class="fs-contentmenu-item-title" flex-box="1">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FsContextmenuList",
|
||||
props: {
|
||||
menulist: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowClick(event) {
|
||||
let target = event.target;
|
||||
while (!target.dataset.value) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
this.$emit("rowClick", target.dataset.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.fs-contentmenu-list {
|
||||
.fs-contentmenu-item {
|
||||
padding: 8px 20px 8px 15px;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #ecf5ff;
|
||||
color: #66b1ff;
|
||||
}
|
||||
.fs-contentmenu-item-title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div v-show="flag" class="fs-contextmenu" :style="style">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FsContextmenu",
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
x: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
y: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
flag: {
|
||||
get() {
|
||||
if (this.visible) {
|
||||
// 注册全局监听事件 [ 目前只考虑鼠标解除触发 ]
|
||||
window.addEventListener("mousedown", this.watchContextmenu);
|
||||
}
|
||||
return this.visible;
|
||||
},
|
||||
set(newVal) {
|
||||
this.$emit("update:visible", newVal);
|
||||
}
|
||||
},
|
||||
style() {
|
||||
return {
|
||||
left: this.x + "px",
|
||||
top: this.y + "px",
|
||||
display: this.visible ? "block" : "none "
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 将菜单放置到body下
|
||||
document.querySelector("body").appendChild(this.$el);
|
||||
},
|
||||
methods: {
|
||||
watchContextmenu(event) {
|
||||
if (!this.$el.contains(event.target) || event.button !== 0) this.flag = false;
|
||||
window.removeEventListener("mousedown", this.watchContextmenu);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.fs-contextmenu {
|
||||
position: absolute;
|
||||
padding: 5px 0;
|
||||
z-index: 2018;
|
||||
background: #fff;
|
||||
border: 1px solid #cfd7e5;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<a-dropdown class="fs-locale-picker">
|
||||
<fs-iconify icon="ion-globe-outline" @click.prevent></fs-iconify>
|
||||
<template #overlay>
|
||||
<a-menu @click="changeLocale">
|
||||
<a-menu-item v-for="item in languages" :key="item.key" :command="item.key">
|
||||
<div class="language-item">
|
||||
<span v-if="item.key === current" class="icon-radio">
|
||||
<span class="iconify" data-icon="ion:radio-button-on" data-inline="false"></span>
|
||||
</span>
|
||||
<span v-else class="icon-radio">
|
||||
<span class="iconify" data-icon="ion:radio-button-off" data-inline="false"></span>
|
||||
</span>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "../../../i18n";
|
||||
import { computed, inject } from "vue";
|
||||
import _ from "lodash-es";
|
||||
export default {
|
||||
name: "FsLocale",
|
||||
setup() {
|
||||
const languages = computed(() => {
|
||||
const map = i18n.global.messages?.value || {};
|
||||
const list = [];
|
||||
_.forEach(map, (item, key) => {
|
||||
list.push({
|
||||
key,
|
||||
label: item.label
|
||||
});
|
||||
});
|
||||
return list;
|
||||
});
|
||||
const current = computed(() => {
|
||||
return i18n.global.locale.value;
|
||||
});
|
||||
|
||||
const routerReload = inject("fn:router.reload");
|
||||
const localeChanged = inject("fn:locale.changed");
|
||||
const changeLocale = (change) => {
|
||||
i18n.global.locale.value = change.key;
|
||||
routerReload();
|
||||
localeChanged(change.key)
|
||||
};
|
||||
return {
|
||||
languages,
|
||||
current,
|
||||
changeLocale
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.locale-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.language-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.icon-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.iconify {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,224 @@
|
|||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ref, watch, onMounted, onUnmounted, resolveComponent, nextTick, defineComponent } from "vue";
|
||||
import getEachDeep from "deepdash-es/getEachDeep";
|
||||
import _ from "lodash-es";
|
||||
import BScroll from "better-scroll";
|
||||
import "./index.less";
|
||||
const eachDeep = getEachDeep(_);
|
||||
|
||||
function useBetterScroll(enabled = true) {
|
||||
let bsRef = ref(null);
|
||||
let asideMenuRef = ref();
|
||||
|
||||
let onOpenChange = () => {};
|
||||
if (enabled) {
|
||||
function bsInit() {
|
||||
if (asideMenuRef.value == null) {
|
||||
return;
|
||||
}
|
||||
bsRef.value = new BScroll(asideMenuRef.value, {
|
||||
mouseWheel: true,
|
||||
click: true,
|
||||
momentum: false,
|
||||
// 如果你愿意可以打开显示滚动条
|
||||
scrollbar: {
|
||||
fade: true,
|
||||
interactive: false
|
||||
},
|
||||
bounce: false
|
||||
});
|
||||
}
|
||||
|
||||
function bsDestroy() {
|
||||
if (bsRef.value != null && bsRef.value.destroy) {
|
||||
try {
|
||||
bsRef.value.destroy();
|
||||
} catch (e) {
|
||||
// console.error(e);
|
||||
} finally {
|
||||
bsRef.value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
bsInit();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
bsDestroy();
|
||||
});
|
||||
onOpenChange = async () => {
|
||||
console.log("onOpenChange");
|
||||
setTimeout(() => {
|
||||
bsRef.value?.refresh();
|
||||
}, 300);
|
||||
};
|
||||
}
|
||||
return {
|
||||
onOpenChange,
|
||||
asideMenuRef
|
||||
};
|
||||
}
|
||||
export default defineComponent({
|
||||
name: "FsMenu",
|
||||
inheritAttrs: true,
|
||||
props: {
|
||||
menus: {},
|
||||
expandSelected: {
|
||||
default: false
|
||||
},
|
||||
scroll: {}
|
||||
},
|
||||
setup(props, ctx) {
|
||||
async function open(path) {
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
if (path.startsWith("http://") || path.startsWith("https://")) {
|
||||
window.open(path);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const navigationResult = await router.push(path);
|
||||
if (navigationResult) {
|
||||
// 导航被阻止
|
||||
} else {
|
||||
// 导航成功 (包括重新导航的情况)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("导航失败", e);
|
||||
}
|
||||
}
|
||||
function onSelect(item) {
|
||||
open(item.key);
|
||||
}
|
||||
|
||||
const FsIcon = resolveComponent("FsIcon");
|
||||
|
||||
const buildMenus = (children) => {
|
||||
const slots = [];
|
||||
if (children == null) {
|
||||
return slots;
|
||||
}
|
||||
for (let sub of children) {
|
||||
const title = () => {
|
||||
if (sub?.meta?.icon) {
|
||||
return (
|
||||
<div class={"menu-item-title"}>
|
||||
<FsIcon class={"anticon"} icon={sub.meta.icon} />
|
||||
<span>{sub.title}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return sub.title;
|
||||
};
|
||||
if (sub.children && sub.children.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const subSlots = {
|
||||
default: () => {
|
||||
return buildMenus(sub.children);
|
||||
},
|
||||
title
|
||||
};
|
||||
function onTitleClick() {
|
||||
if (sub.path && ctx.attrs.mode === "horizontal") {
|
||||
open(sub.path);
|
||||
}
|
||||
}
|
||||
slots.push(<a-sub-menu key={sub.index} v-slots={subSlots} onTitleClick={onTitleClick} />);
|
||||
} else {
|
||||
slots.push(
|
||||
<a-menu-item key={sub.path} title={sub.title}>
|
||||
{title}
|
||||
</a-menu-item>
|
||||
);
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
};
|
||||
const slots = {
|
||||
default() {
|
||||
return buildMenus(props.menus);
|
||||
}
|
||||
};
|
||||
const selectedKeys = ref([]);
|
||||
const openKeys = ref([]);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
function openSelectedParents(fullPath) {
|
||||
if (!props.expandSelected) {
|
||||
return;
|
||||
}
|
||||
if (props.menus == null) {
|
||||
return;
|
||||
}
|
||||
const keys = [];
|
||||
let changed = false;
|
||||
eachDeep(props.menus, (value, key, parent, context) => {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (value.path === fullPath) {
|
||||
_.forEach(context.parents, (item) => {
|
||||
if (item.value instanceof Array) {
|
||||
return;
|
||||
}
|
||||
keys.push(item.value.index);
|
||||
});
|
||||
}
|
||||
});
|
||||
if (keys.length > 0) {
|
||||
for (let key of keys) {
|
||||
if (openKeys.value.indexOf(key) === -1) {
|
||||
openKeys.value.push(key);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
const { asideMenuRef, onOpenChange } = useBetterScroll(props.scroll);
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return route.fullPath;
|
||||
},
|
||||
(path) => {
|
||||
// path = route.fullPath;
|
||||
selectedKeys.value = [path];
|
||||
const changed = openSelectedParents(path);
|
||||
if (changed) {
|
||||
onOpenChange();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
const menu = (
|
||||
<a-menu
|
||||
mode={"inline"}
|
||||
theme={"light"}
|
||||
v-slots={slots}
|
||||
onClick={onSelect}
|
||||
onOpenChange={onOpenChange}
|
||||
v-models={[
|
||||
[openKeys.value, "openKeys"],
|
||||
[selectedKeys.value, "selectedKeys"]
|
||||
]}
|
||||
{...ctx.attrs}
|
||||
/>
|
||||
);
|
||||
const classNames = { "fs-menu-wrapper": true, "fs-menu-better-scroll": props.scroll };
|
||||
return (
|
||||
<div ref={asideMenuRef} class={classNames}>
|
||||
{menu}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
.fs-menu-wrapper{
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
&.fs-menu-better-scroll{
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.menu-item-title{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<div v-if="showSourceLink" class="fs-source-link-group">
|
||||
<div class="fs-source-link" @click="goSource('https://gitee.com')">本页源码(Gitee)</div>
|
||||
<div class="fs-source-link" @click="goSource('https://github.com')">本页源码(Github)</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
export default defineComponent({
|
||||
name: "FsSourceLink",
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const showSourceLink = ref(false);
|
||||
watch(
|
||||
() => {
|
||||
return router.currentRoute.value.fullPath;
|
||||
},
|
||||
(value) => {
|
||||
showSourceLink.value = value !== "/index";
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const middle = "/fast-crud/fs-admin-antdv/tree/main/src/views";
|
||||
function goSource(prefix) {
|
||||
const path = router.currentRoute.value.fullPath;
|
||||
window.open(prefix + middle + path + "/index.vue");
|
||||
}
|
||||
return {
|
||||
goSource,
|
||||
showSourceLink
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.fs-source-link-group {
|
||||
position: fixed;
|
||||
right: 3px;
|
||||
bottom: 20px;
|
||||
.fs-source-link {
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
border-radius: 5px 0 0 5px;
|
||||
padding: 5px;
|
||||
background: #666;
|
||||
color: #fff;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,295 @@
|
|||
<template>
|
||||
<div class="fs-multiple-page-control-group">
|
||||
<div class="fs-multiple-page-control-content">
|
||||
<div class="fs-multiple-page-control-content-inner">
|
||||
<a-tabs
|
||||
class="fs-multiple-page-control fs-multiple-page-sort"
|
||||
:active-key="page.getCurrent"
|
||||
type="editable-card"
|
||||
hide-add
|
||||
@tabClick="handleClick"
|
||||
@edit="handleTabEdit"
|
||||
@contextmenu="handleContextmenu"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="item in page.getOpened"
|
||||
:key="item.fullPath"
|
||||
:tab="item.meta?.title || '未命名'"
|
||||
:name="item.fullPath"
|
||||
:closable="isTabClosable(item)"
|
||||
/>
|
||||
</a-tabs>
|
||||
<!-- <fs-contextmenu v-model:visible="contextmenuFlag" :x="contentmenuX" :y="contentmenuY">-->
|
||||
<!-- <fs-contextmenu-list-->
|
||||
<!-- :menulist="tagName === '/index' ? contextmenuListIndex : contextmenuList"-->
|
||||
<!-- @rowClick="contextmenuClick"-->
|
||||
<!-- />-->
|
||||
<!-- </fs-contextmenu>-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fs-multiple-page-control-btn">
|
||||
<a-dropdown-button class="control-btn-dropdown" split-button @click="closeAll">
|
||||
<span class="iconify" data-icon="ion:close-circle" data-inline="false"></span>
|
||||
<template #icon><DownOutlined /></template>
|
||||
<template #overlay>
|
||||
<a-menu @click="(command) => handleControlItemClick(command)">
|
||||
<a-menu-item key="left">
|
||||
<fs-icon name="arrow-left" class="fs-mr-10" />
|
||||
关闭左侧
|
||||
</a-menu-item>
|
||||
<a-menu-item key="right">
|
||||
<fs-icon name="arrow-right" class="fs-mr-10" />
|
||||
关闭右侧
|
||||
</a-menu-item>
|
||||
<a-menu-item key="other">
|
||||
<fs-icon name="times" class="fs-mr-10" />
|
||||
关闭其它
|
||||
</a-menu-item>
|
||||
<a-menu-item key="all">
|
||||
<fs-icon name="times-circle" class="fs-mr-10" />
|
||||
全部关闭
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sortable from "sortablejs";
|
||||
import { usePageStore } from "../../../store/modules/page";
|
||||
import { computed } from "vue";
|
||||
export default {
|
||||
name: "FsTabs",
|
||||
components: {
|
||||
// FsContextmenu: () => import("../contextmenu/index.vue"),
|
||||
// FsContextmenuList: () => import("../contextmenu/components/contentmenuList/index.vue")
|
||||
},
|
||||
setup() {
|
||||
const pageStore = usePageStore();
|
||||
|
||||
const actions = {
|
||||
close: pageStore.close,
|
||||
closeLeft: pageStore.closeLeft,
|
||||
closeRight: pageStore.closeRight,
|
||||
closeOther: pageStore.closeOther,
|
||||
closeAll: pageStore.closeAll,
|
||||
openedSort: pageStore.openedSort
|
||||
};
|
||||
console.log("opened", pageStore.getOpened);
|
||||
const computeOpened = computed(() => {
|
||||
console.log("opened", pageStore.getOpened);
|
||||
return pageStore.getOpened;
|
||||
});
|
||||
|
||||
return {
|
||||
page: pageStore,
|
||||
...actions,
|
||||
computeOpened
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
contextmenuFlag: false,
|
||||
contentmenuX: 0,
|
||||
contentmenuY: 0,
|
||||
contextmenuListIndex: [{ icon: "times-circle", title: "关闭全部", value: "all" }],
|
||||
contextmenuList: [
|
||||
{ icon: "arrow-left", title: "关闭左侧", value: "left" },
|
||||
{ icon: "arrow-right", title: "关闭右侧", value: "right" },
|
||||
{ icon: "times", title: "关闭其它", value: "other" },
|
||||
{ icon: "times-circle", title: "关闭全部", value: "all" }
|
||||
],
|
||||
tagName: "/index"
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const el = document.querySelectorAll(".fs-multiple-page-sort .el-tabs__nav")[0];
|
||||
// Sortable.create(el, {
|
||||
// onEnd: (evt) => {
|
||||
// const { oldIndex, newIndex } = evt;
|
||||
// this.openedSort({ oldIndex, newIndex });
|
||||
// }
|
||||
// });
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 计算某个标签页是否可关闭
|
||||
* @param {Object} page 其中一个标签页
|
||||
*/
|
||||
isTabClosable(page) {
|
||||
return page.name !== "index";
|
||||
},
|
||||
/**
|
||||
* @description 右键菜单功能点击
|
||||
* @param {Object} event 事件
|
||||
*/
|
||||
handleContextmenu(event) {
|
||||
let target = event.target;
|
||||
// fix https://github.com/fs-projects/fs-admin/issues/54
|
||||
let flag = false;
|
||||
if (target.className.indexOf("el-tabs__item") > -1) flag = true;
|
||||
else if (target.parentNode.className.indexOf("el-tabs__item") > -1) {
|
||||
target = target.parentNode;
|
||||
flag = true;
|
||||
}
|
||||
if (flag) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.contentmenuX = event.clientX;
|
||||
this.contentmenuY = event.clientY;
|
||||
this.tagName = target.getAttribute("aria-controls").slice(5);
|
||||
this.contextmenuFlag = true;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 右键菜单的 row-click 事件
|
||||
* @param {String} command 事件类型
|
||||
*/
|
||||
contextmenuClick(command) {
|
||||
this.handleControlItemClick(command, this.tagName);
|
||||
},
|
||||
/**
|
||||
* @description 接收点击关闭控制上选项的事件
|
||||
* @param {String} command 事件类型
|
||||
* @param {String} tagName tab 名称
|
||||
*/
|
||||
handleControlItemClick(command, tagName = null) {
|
||||
//if (tagName) this.contextmenuFlag = false;
|
||||
const params = { pageSelect: tagName };
|
||||
switch (command.key) {
|
||||
case "left":
|
||||
this.closeLeft(params);
|
||||
break;
|
||||
case "right":
|
||||
this.closeRight(params);
|
||||
break;
|
||||
case "other":
|
||||
this.closeOther(params);
|
||||
break;
|
||||
case "all":
|
||||
this.closeAll();
|
||||
break;
|
||||
default:
|
||||
this.$message.error("无效的操作");
|
||||
break;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 接收点击 tab 标签的事件
|
||||
* @param {object} tab 标签
|
||||
* @param {object} event 事件
|
||||
*/
|
||||
handleClick(tab) {
|
||||
// 找到点击的页面在 tag 列表里是哪个
|
||||
const page = this.page.getOpened.find((page) => page.fullPath === tab);
|
||||
if (page) {
|
||||
const { name, params, query } = page;
|
||||
this.$router.push({ name, params, query });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 点击 tab 上的删除按钮触发这里
|
||||
* @param {String} tagName tab 名称
|
||||
*/
|
||||
handleTabEdit(tagName, action) {
|
||||
if (action === "remove") {
|
||||
this.close({ tagName });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
//common
|
||||
.fs-multiple-page-control-group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
.fs-multiple-page-control-content {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.fs-multiple-page-control-btn {
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
//antdv
|
||||
.fs-multiple-page-control-group {
|
||||
.ant-tabs-bar {
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.ant-tabs-top > .ant-tabs-nav,
|
||||
.ant-tabs-bottom > .ant-tabs-nav,
|
||||
.ant-tabs-top > div > .ant-tabs-nav,
|
||||
.ant-tabs-bottom > div > .ant-tabs-nav {
|
||||
margin: 0;
|
||||
}
|
||||
.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-nav {
|
||||
.ant-tabs-tab {
|
||||
margin-right: 0;
|
||||
border-right: 0;
|
||||
|
||||
&:first-of-type {
|
||||
border-top-left-radius: 2px;
|
||||
}
|
||||
&:last-of-type {
|
||||
border-top-right-radius: 2px;
|
||||
border-right: 1px;
|
||||
}
|
||||
|
||||
&:not(.ant-tabs-tab-active) {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
.ant-tabs-tab-active {
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
}
|
||||
.ant-tabs-close-x {
|
||||
display: none;
|
||||
}
|
||||
.ant-tabs-tab {
|
||||
&:hover {
|
||||
.ant-tabs-close-x {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-tabs-tab-active {
|
||||
.ant-tabs-close-x {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.fs-multiple-page-control-btn {
|
||||
display: flex;
|
||||
.ant-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
height: 100%;
|
||||
color: #666;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.control-btn-dropdown {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-tab-arrow-show {
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
.ant-tabs-tab-prev {
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.ant-tabs-tab-next {
|
||||
border-left: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
//element
|
||||
</style>
|
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<div class="fs-theme-color-picker">
|
||||
<h4>主题色</h4>
|
||||
<div class="fs-theme-colors">
|
||||
<a-tooltip v-for="(item, index) in colorList" :key="index" class="fs-theme-color-item">
|
||||
<template #title>
|
||||
{{ item.key }}
|
||||
</template>
|
||||
<a-tag :color="item.color" @click="changeColor(item.color)">
|
||||
<CheckOutlined v-if="item.color === primaryColor" />
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref } from "vue";
|
||||
const colorListDefine = [
|
||||
{
|
||||
key: "薄暮",
|
||||
color: "#f5222d"
|
||||
},
|
||||
{
|
||||
key: "火山",
|
||||
color: "#fa541c"
|
||||
},
|
||||
{
|
||||
key: "日暮",
|
||||
color: "#faad14"
|
||||
},
|
||||
{
|
||||
key: "明青",
|
||||
color: "#13c2c2"
|
||||
},
|
||||
{
|
||||
key: "极光绿",
|
||||
color: "#52c41a"
|
||||
},
|
||||
{
|
||||
key: "拂晓蓝(默认)",
|
||||
color: "#1890ff"
|
||||
},
|
||||
{
|
||||
key: "极客蓝",
|
||||
color: "#2f54eb"
|
||||
},
|
||||
{
|
||||
key: "酱紫",
|
||||
color: "#722ed1"
|
||||
}
|
||||
];
|
||||
export default defineComponent({
|
||||
name: "FsThemeColorPicker",
|
||||
props: {
|
||||
primaryColor: {
|
||||
default: "#1890ff"
|
||||
}
|
||||
},
|
||||
emits: ["change"],
|
||||
setup(props, ctx) {
|
||||
const colorList = ref(colorListDefine);
|
||||
function changeColor(color) {
|
||||
ctx.emit("change", color);
|
||||
}
|
||||
return {
|
||||
colorList,
|
||||
changeColor
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.fs-theme-color-picker {
|
||||
.fs-theme-colors {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
.fs-theme-color-item {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div class="fs-theme" @click="show()">
|
||||
<fs-iconify icon="ion:sparkles-outline" />
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
title="主题设置"
|
||||
placement="right"
|
||||
width="350px"
|
||||
:closable="false"
|
||||
@after-visible-change="afterVisibleChange"
|
||||
>
|
||||
<fs-theme-color-picker
|
||||
:primary-color="setting.getTheme.primaryColor"
|
||||
@change="setting.setPrimaryColor"
|
||||
></fs-theme-color-picker>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, defineComponent } from "vue";
|
||||
import FsThemeColorPicker from "./color-picker.vue";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
|
||||
export default defineComponent({
|
||||
name: "FsTheme",
|
||||
components: { FsThemeColorPicker },
|
||||
setup() {
|
||||
const visible = ref(false);
|
||||
function afterVisibleChange() {}
|
||||
function show() {
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
const setting = useSettingStore();
|
||||
return {
|
||||
visible,
|
||||
show,
|
||||
afterVisibleChange,
|
||||
setting
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.fs-theme {
|
||||
}
|
||||
.fs-theme-drawer {
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<a-dropdown>
|
||||
<div class="fs-user-info">您好,{{ userStore.getUserInfo?.nickName }}</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<div @click="doLogout">注销登录</div>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { useUserStore } from "/src/store/modules/user";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
export default defineComponent({
|
||||
name: "FsUserInfo",
|
||||
setup() {
|
||||
const userStore = useUserStore();
|
||||
console.log("user", userStore);
|
||||
const { t } = useI18n();
|
||||
function doLogout() {
|
||||
Modal.confirm({
|
||||
iconType: "warning",
|
||||
title: t("app.login.logoutTip"),
|
||||
content: t("app.login.logoutMessage"),
|
||||
onOk: async () => {
|
||||
await userStore.logout(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
userStore,
|
||||
doLogout
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,230 @@
|
|||
<template xmlns:w="http://www.w3.org/1999/xhtml">
|
||||
<a-layout class="fs-framework">
|
||||
<a-layout-sider v-model:collapsed="asideCollapsed" :trigger="null" collapsible>
|
||||
<div class="header-logo">
|
||||
<img src="/images/logo/rect-black.svg" />
|
||||
<span v-if="!asideCollapsed" class="title">FsAdmin</span>
|
||||
</div>
|
||||
<div class="aside-menu">
|
||||
<fs-menu :scroll="true" :menus="asideMenus" :expand-selected="!asideCollapsed" />
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
|
||||
<a-layout class="layout-body">
|
||||
<a-layout-header class="header">
|
||||
<div class="header-buttons">
|
||||
<div class="menu-fold" @click="asideCollapsedToggle">
|
||||
<MenuUnfoldOutlined v-if="asideCollapsed" />
|
||||
<MenuFoldOutlined v-else />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fs-menu
|
||||
class="header-menu"
|
||||
mode="horizontal"
|
||||
:expand-selected="false"
|
||||
:selectable="false"
|
||||
:menus="frameworkMenus"
|
||||
/>
|
||||
<div class="header-right header-buttons">
|
||||
<!-- <button-->
|
||||
<!-- w:bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"-->
|
||||
<!-- w:text="sm white"-->
|
||||
<!-- w:font="mono light"-->
|
||||
<!-- w:p="y-2 x-4"-->
|
||||
<!-- w:border="2 rounded blue-200"-->
|
||||
<!-- >-->
|
||||
<!-- Button-->
|
||||
<!-- </button>-->
|
||||
<fs-menu
|
||||
class="header-menu"
|
||||
mode="horizontal"
|
||||
:expand-selected="false"
|
||||
:selectable="false"
|
||||
:menus="headerMenus"
|
||||
/>
|
||||
<fs-locale class="btn" />
|
||||
<!-- <fs-theme-set class="btn" />-->
|
||||
<fs-user-info class="btn" />
|
||||
</div>
|
||||
</a-layout-header>
|
||||
<fs-tabs></fs-tabs>
|
||||
<a-layout-content class="fs-framework-content">
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<transition name="fade-transverse">
|
||||
<keep-alive :include="keepAlive">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</template>
|
||||
</router-view>
|
||||
</a-layout-content>
|
||||
<a-layout-footer class="fs-framework-footer"
|
||||
>by fast-crud
|
||||
<fs-source-link />
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, onErrorCaptured, ref } from "vue";
|
||||
import FsMenu from "./components/menu/index.jsx";
|
||||
import FsLocale from "./components/locale/index.vue";
|
||||
import FsSourceLink from "./components/source-link/index.vue";
|
||||
import FsUserInfo from "./components/user-info/index.vue";
|
||||
import FsTabs from "./components/tabs/index.vue";
|
||||
import { useResourceStore } from "../store/modules/resource";
|
||||
import { usePageStore } from "/@/store/modules/page";
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons-vue";
|
||||
import FsThemeSet from "/@/layout/components/theme/index.vue";
|
||||
import { notification } from "ant-design-vue";
|
||||
export default {
|
||||
name: "LayoutFramework",
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: { FsThemeSet, MenuFoldOutlined, MenuUnfoldOutlined, FsMenu, FsLocale, FsSourceLink, FsUserInfo, FsTabs },
|
||||
setup() {
|
||||
const resourceStore = useResourceStore();
|
||||
const frameworkMenus = computed(() => {
|
||||
return resourceStore.getFrameworkMenus;
|
||||
});
|
||||
const headerMenus = computed(() => {
|
||||
return resourceStore.getHeaderMenus;
|
||||
});
|
||||
const asideMenus = computed(() => {
|
||||
return resourceStore.getAsideMenus;
|
||||
});
|
||||
|
||||
const pageStore = usePageStore();
|
||||
const keepAlive = pageStore.keepAlive;
|
||||
|
||||
const asideCollapsed = ref(false);
|
||||
function asideCollapsedToggle() {
|
||||
asideCollapsed.value = !asideCollapsed.value;
|
||||
}
|
||||
onErrorCaptured((e) => {
|
||||
console.error("ErrorCaptured:", e);
|
||||
notification.error({ message: e.message });
|
||||
//阻止错误向上传递
|
||||
return false;
|
||||
});
|
||||
return {
|
||||
frameworkMenus,
|
||||
headerMenus,
|
||||
asideMenus,
|
||||
keepAlive,
|
||||
asideCollapsed,
|
||||
asideCollapsedToggle
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import "../style/theme/index.less";
|
||||
.fs-framework {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
.header-logo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
// margin: 16px 24px 16px 0;
|
||||
//background: rgba(255, 255, 255, 0.3);
|
||||
img {
|
||||
height: 80%;
|
||||
}
|
||||
.title {
|
||||
margin-left: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.fs-framework-content {
|
||||
flex: 1;
|
||||
border-left: 1px solid #f0f0f0;
|
||||
}
|
||||
.fs-framework-footer {
|
||||
border-left: 1px solid #f0f0f0;
|
||||
padding: 10px 20px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 14px;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > * {
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
& > .btn {
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.header-right {
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.header-menu {
|
||||
flex: 1;
|
||||
}
|
||||
.aside-menu {
|
||||
flex: 1;
|
||||
ui {
|
||||
height: 100%;
|
||||
}
|
||||
overflow: hidden;
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
.layout-body {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
//antdv
|
||||
.fs-framework {
|
||||
&.ant-layout {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.ant-layout-sider-children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ant-layout-sider {
|
||||
// border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
.ant-layout-header {
|
||||
height: 50px;
|
||||
padding: 0 10px;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.ant-layout-content {
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
//element
|
||||
.fs-framework {
|
||||
.el-aside {
|
||||
.el-menu {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div id="userLayout" :class="['user-layout-wrapper']">
|
||||
<div class="login-container flex-center">
|
||||
<div class="user-layout-lang"></div>
|
||||
<div class="user-layout-content">
|
||||
<div class="top flex flex-col items-center justify-center">
|
||||
<div class="header flex flex-row items-center">
|
||||
<img src="/images/logo/rect-black.svg" class="logo" alt="logo" />
|
||||
<span class="title">FsAdmin</span>
|
||||
</div>
|
||||
<div class="desc">fast-crud,开发crud快如闪电</div>
|
||||
</div>
|
||||
|
||||
<router-view />
|
||||
|
||||
<div class="footer">
|
||||
<div class="links">
|
||||
<a href="_self">帮助</a>
|
||||
<a href="_self">隐私</a>
|
||||
<a href="_self">条款</a>
|
||||
</div>
|
||||
<div class="copyright">Copyright © 2021 Greper</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "LayoutOutside"
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#userLayout.user-layout-wrapper {
|
||||
height: 100%;
|
||||
|
||||
&.mobile {
|
||||
.container {
|
||||
.main {
|
||||
max-width: 368px;
|
||||
width: 98%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: #f0f2f5 url(/src/assets/background.svg) no-repeat 50%;
|
||||
background-size: 100%;
|
||||
//padding: 50px 0 84px;
|
||||
position: relative;
|
||||
|
||||
.user-layout-lang {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
text-align: right;
|
||||
|
||||
.select-lang-trigger {
|
||||
cursor: pointer;
|
||||
padding: 12px;
|
||||
margin-right: 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.user-layout-content {
|
||||
padding: 32px 0 24px;
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
|
||||
.header {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
margin-left: -12px;
|
||||
margin-top: -10px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 44px;
|
||||
vertical-align: top;
|
||||
margin-right: 16px;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 33px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-family: Avenir, "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
min-width: 260px;
|
||||
width: 368px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
// position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
padding: 0 16px;
|
||||
margin: 48px 0 24px;
|
||||
text-align: center;
|
||||
|
||||
.links {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
a {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
transition: all 0.3s;
|
||||
&:not(:last-child) {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.copyright {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "LayoutPass"
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,23 @@
|
|||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import Antd from "ant-design-vue";
|
||||
import "ant-design-vue/dist/antd.less";
|
||||
// import "virtual:windi.css";
|
||||
import "./style/common.less";
|
||||
import "./mock";
|
||||
import i18n from "./i18n";
|
||||
import store from "./store";
|
||||
|
||||
import plugin from "./plugin/";
|
||||
import components from "./components";
|
||||
// @ts-ignore
|
||||
const app = createApp(App);
|
||||
// 尽量
|
||||
app.use(Antd);
|
||||
app.use(router);
|
||||
app.use(i18n);
|
||||
app.use(store);
|
||||
app.use(plugin, { i18n });
|
||||
app.use(components);
|
||||
app.mount("#app");
|
|
@ -0,0 +1,280 @@
|
|||
import _ from "lodash-es";
|
||||
function copyList(originList, newList, options, parentId) {
|
||||
for (const item of originList) {
|
||||
const newItem = { ...item, parentId };
|
||||
newItem.id = ++options.idGenerator;
|
||||
newList.push(newItem);
|
||||
if (item.children != null) {
|
||||
newItem.children = [];
|
||||
copyList(item.children, newItem.children, options, newItem.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function delById(req, list) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const item = list[i];
|
||||
console.log("remove i", i, req, req.params.id, item.id);
|
||||
if (item.id === parseInt(req.params.id)) {
|
||||
console.log("remove i", i);
|
||||
list.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
if (item.children != null && item.children.length > 0) {
|
||||
delById(req, item.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findById(id, list) {
|
||||
for (const item of list) {
|
||||
if (item.id === id) {
|
||||
return item;
|
||||
}
|
||||
if (item.children != null && item.children.length > 0) {
|
||||
const sub = findById(id, item.children);
|
||||
if (sub != null) {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export default {
|
||||
findById,
|
||||
buildMock(options) {
|
||||
const name = options.name;
|
||||
if (options.copyTimes == null) {
|
||||
options.copyTimes = 29;
|
||||
}
|
||||
const list = [];
|
||||
for (let i = 0; i < options.copyTimes; i++) {
|
||||
copyList(options.list, list, options);
|
||||
}
|
||||
options.list = list;
|
||||
return [
|
||||
{
|
||||
path: "/mock/" + name + "/page",
|
||||
method: "get",
|
||||
handle(req) {
|
||||
let data = [...list];
|
||||
let limit = 20;
|
||||
let offset = 0;
|
||||
for (const item of list) {
|
||||
if (item.children != null && item.children.length === 0) {
|
||||
item.hasChildren = false;
|
||||
item.lazy = false;
|
||||
}
|
||||
}
|
||||
let orderProp, orderAsc;
|
||||
if (req && req.body) {
|
||||
const { page, query, sort } = req.body;
|
||||
if (page.limit != null) {
|
||||
limit = parseInt(page.limit);
|
||||
}
|
||||
if (page.offset != null) {
|
||||
offset = parseInt(page.offset);
|
||||
}
|
||||
orderProp = sort.prop;
|
||||
orderAsc = sort.asc;
|
||||
|
||||
if (Object.keys(query).length > 0) {
|
||||
data = list.filter((item) => {
|
||||
let allFound = true; // 是否所有条件都符合
|
||||
for (const key in query) {
|
||||
// 判定某一个条件
|
||||
const value = query[key];
|
||||
if (value == null || value === "") {
|
||||
continue;
|
||||
}
|
||||
if (value instanceof Array) {
|
||||
// 如果条件中的value是数组的话,只要查到一个就行
|
||||
if (value.length === 0) {
|
||||
continue;
|
||||
}
|
||||
let found = false;
|
||||
for (const i of value) {
|
||||
if (item[key] instanceof Array) {
|
||||
for (const j of item[key]) {
|
||||
if (i === j) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
} else if (item[key] === i || (typeof item[key] === "string" && item[key].indexOf(i + "") >= 0)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
allFound = false;
|
||||
}
|
||||
} else if (value instanceof Object) {
|
||||
for (const key2 in value) {
|
||||
const v = value[key2];
|
||||
if (v && item[key] && v !== item[key][key2]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (item[key] !== value) {
|
||||
allFound = false;
|
||||
}
|
||||
}
|
||||
return allFound;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const start = offset;
|
||||
let end = offset + limit;
|
||||
if (data.length < end) {
|
||||
end = data.length;
|
||||
}
|
||||
|
||||
if (orderProp) {
|
||||
// 排序
|
||||
data.sort((a, b) => {
|
||||
let ret = 0;
|
||||
if (a[orderProp] > b[orderProp]) {
|
||||
ret = 1;
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
return orderAsc ? ret : -ret;
|
||||
});
|
||||
}
|
||||
|
||||
const records = data.slice(start, end);
|
||||
const lastOffset = data.length - (data.length % limit);
|
||||
if (offset > lastOffset) {
|
||||
offset = lastOffset;
|
||||
}
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: {
|
||||
records: records,
|
||||
total: data.length,
|
||||
limit,
|
||||
offset
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/" + name + "/get",
|
||||
method: "get",
|
||||
handle(req) {
|
||||
let id = req.params.id;
|
||||
id = parseInt(id);
|
||||
let current = null;
|
||||
for (const item of list) {
|
||||
if (item.id === id) {
|
||||
current = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: current
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/" + name + "/add",
|
||||
method: "post",
|
||||
handle(req) {
|
||||
req.body.id = ++options.idGenerator;
|
||||
list.unshift(req.body);
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: req.body.id
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/" + name + "/update",
|
||||
method: "post",
|
||||
handle(req) {
|
||||
const item = findById(req.body.id, list);
|
||||
if (item) {
|
||||
_.mergeWith(item, req.body, (objValue, srcValue) => {
|
||||
if (srcValue == null) {
|
||||
return;
|
||||
}
|
||||
// 如果被合并对象为数组,则直接被覆盖对象覆盖,只要覆盖对象不为空
|
||||
if (_.isArray(objValue)) {
|
||||
return srcValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/" + name + "/delete",
|
||||
method: "post",
|
||||
handle(req) {
|
||||
delById(req, list);
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/" + name + "/batchDelete",
|
||||
method: "post",
|
||||
handle(req) {
|
||||
const ids = req.body.ids;
|
||||
for (let i = list.length - 1; i >= 0; i--) {
|
||||
const item = list[i];
|
||||
if (ids.indexOf(item.id) >= 0) {
|
||||
list.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/" + name + "/delete",
|
||||
method: "post",
|
||||
handle(req) {
|
||||
delById(req, list);
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/" + name + "/all",
|
||||
method: "post",
|
||||
handle(req) {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: list
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
|
@ -0,0 +1,268 @@
|
|||
export default [
|
||||
{
|
||||
value: "zhinan",
|
||||
label: "指南",
|
||||
children: [
|
||||
{
|
||||
value: "shejiyuanze",
|
||||
label: "设计原则",
|
||||
children: [
|
||||
{
|
||||
value: "yizhi",
|
||||
label: "一致"
|
||||
},
|
||||
{
|
||||
value: "fankui",
|
||||
label: "反馈"
|
||||
},
|
||||
{
|
||||
value: "xiaolv",
|
||||
label: "效率"
|
||||
},
|
||||
{
|
||||
value: "kekong",
|
||||
label: "可控"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "daohang",
|
||||
label: "导航",
|
||||
children: [
|
||||
{
|
||||
value: "cexiangdaohang",
|
||||
label: "侧向导航"
|
||||
},
|
||||
{
|
||||
value: "dingbudaohang",
|
||||
label: "顶部导航"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "zujian",
|
||||
label: "组件",
|
||||
children: [
|
||||
{
|
||||
value: "basic",
|
||||
label: "Basic",
|
||||
children: [
|
||||
{
|
||||
value: "layout",
|
||||
label: "Layout 布局"
|
||||
},
|
||||
{
|
||||
value: "color",
|
||||
label: "Color 色彩"
|
||||
},
|
||||
{
|
||||
value: "typography",
|
||||
label: "Typography 字体"
|
||||
},
|
||||
{
|
||||
value: "icon",
|
||||
label: "Icon 图标"
|
||||
},
|
||||
{
|
||||
value: "button",
|
||||
label: "Button 按钮"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "form",
|
||||
label: "Form",
|
||||
children: [
|
||||
{
|
||||
value: "radio",
|
||||
label: "Radio 单选框"
|
||||
},
|
||||
{
|
||||
value: "checkbox",
|
||||
label: "Checkbox 多选框"
|
||||
},
|
||||
{
|
||||
value: "input",
|
||||
label: "Input 输入框"
|
||||
},
|
||||
{
|
||||
value: "input-number",
|
||||
label: "InputNumber 计数器"
|
||||
},
|
||||
{
|
||||
value: "select",
|
||||
label: "Select 选择器"
|
||||
},
|
||||
{
|
||||
value: "cascader",
|
||||
label: "Cascader 级联选择器"
|
||||
},
|
||||
{
|
||||
value: "switch",
|
||||
label: "Switch 开关"
|
||||
},
|
||||
{
|
||||
value: "slider",
|
||||
label: "Slider 滑块"
|
||||
},
|
||||
{
|
||||
value: "time-picker",
|
||||
label: "TimePicker 时间选择器"
|
||||
},
|
||||
{
|
||||
value: "date-picker",
|
||||
label: "DatePicker 日期选择器"
|
||||
},
|
||||
{
|
||||
value: "datetime-picker",
|
||||
label: "DateTimePicker 日期时间选择器"
|
||||
},
|
||||
{
|
||||
value: "upload",
|
||||
label: "Upload 上传"
|
||||
},
|
||||
{
|
||||
value: "rate",
|
||||
label: "Rate 评分"
|
||||
},
|
||||
{
|
||||
value: "form1",
|
||||
label: "Form 表单"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "data",
|
||||
label: "Data",
|
||||
children: [
|
||||
{
|
||||
value: "table",
|
||||
label: "Table 表格"
|
||||
},
|
||||
{
|
||||
value: "tag",
|
||||
label: "Tag 标签"
|
||||
},
|
||||
{
|
||||
value: "progress",
|
||||
label: "Progress 进度条"
|
||||
},
|
||||
{
|
||||
value: "tree",
|
||||
label: "Tree 树形控件"
|
||||
},
|
||||
{
|
||||
value: "pagination",
|
||||
label: "Pagination 分页"
|
||||
},
|
||||
{
|
||||
value: "badge",
|
||||
label: "Badge 标记"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "notice",
|
||||
label: "Notice",
|
||||
children: [
|
||||
{
|
||||
value: "alert",
|
||||
label: "Alert 警告"
|
||||
},
|
||||
{
|
||||
value: "loading",
|
||||
label: "Loading 加载"
|
||||
},
|
||||
{
|
||||
value: "message",
|
||||
label: "Message 消息提示"
|
||||
},
|
||||
{
|
||||
value: "message-box",
|
||||
label: "MessageBox 弹框"
|
||||
},
|
||||
{
|
||||
value: "notification",
|
||||
label: "Notification 通知"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "navigation",
|
||||
label: "Navigation",
|
||||
children: [
|
||||
{
|
||||
value: "menu",
|
||||
label: "NavMenu 导航菜单"
|
||||
},
|
||||
{
|
||||
value: "tabs",
|
||||
label: "Tabs 标签页"
|
||||
},
|
||||
{
|
||||
value: "breadcrumb",
|
||||
label: "Breadcrumb 面包屑"
|
||||
},
|
||||
{
|
||||
value: "dropdown",
|
||||
label: "Dropdown 下拉菜单"
|
||||
},
|
||||
{
|
||||
value: "steps",
|
||||
label: "Steps 步骤条"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "others",
|
||||
label: "Others",
|
||||
children: [
|
||||
{
|
||||
value: "dialog",
|
||||
label: "Dialog 对话框"
|
||||
},
|
||||
{
|
||||
value: "tooltip",
|
||||
label: "Tooltip 文字提示"
|
||||
},
|
||||
{
|
||||
value: "popover",
|
||||
label: "Popover 弹出框"
|
||||
},
|
||||
{
|
||||
value: "card",
|
||||
label: "Card 卡片"
|
||||
},
|
||||
{
|
||||
value: "carousel",
|
||||
label: "Carousel 走马灯"
|
||||
},
|
||||
{
|
||||
value: "collapse",
|
||||
label: "Collapse 折叠面板"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: "ziyuan",
|
||||
label: "资源",
|
||||
children: [
|
||||
{
|
||||
value: "axure",
|
||||
label: "Axure Components"
|
||||
},
|
||||
{
|
||||
value: "sketch",
|
||||
label: "Sketch Templates"
|
||||
},
|
||||
{
|
||||
value: "jiaohu",
|
||||
label: "组件交互文档"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
|
@ -0,0 +1,123 @@
|
|||
import cascaderData from "./cascader-data";
|
||||
import pcaDataLittle from "./pca-data-little";
|
||||
import { TreeNodesLazyLoader, getPcaData } from "./pcas-data";
|
||||
|
||||
const openStatus = [
|
||||
{ value: "1", label: "打开", color: "success",icon:"ion:radio-button-on" },
|
||||
{ value: "2", label: "停止", color: "cyan" },
|
||||
{ value: "0", label: "关闭", color: "red",icon:"ion:radio-button-off" }
|
||||
];
|
||||
|
||||
const moreOpenStatus = [
|
||||
{ value: "1", label: "打开(open)", color: "success" },
|
||||
{ value: "2", label: "停止(stop)", color: "cyan" },
|
||||
{ value: "0", label: "关闭(close)", color: "red" }
|
||||
];
|
||||
|
||||
const textStatus = [
|
||||
{ id: "1", text: "打开", color: "success" },
|
||||
{ id: "2", text: "停止", color: "cyan" },
|
||||
{ id: "0", text: "关闭", color: "red" }
|
||||
];
|
||||
|
||||
export function GetTreeChildrenByParentId(parentId) {
|
||||
return TreeNodesLazyLoader.getChildren(parentId);
|
||||
}
|
||||
|
||||
export function GetNodesByValues(values) {
|
||||
return TreeNodesLazyLoader.getNodesByValues(values);
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
path: "/mock/dicts/OpenStatusEnum",
|
||||
method: "get",
|
||||
handle() {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: openStatus
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/dicts/_OpenStatusEnum2",
|
||||
method: "get",
|
||||
handle() {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: textStatus
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/dicts/moreOpenStatusEnum",
|
||||
method: "get",
|
||||
handle() {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: moreOpenStatus
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/dicts/cascaderData",
|
||||
method: "get",
|
||||
handle() {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: cascaderData
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/dicts/pca",
|
||||
method: "get",
|
||||
async handle() {
|
||||
const data = await getPcaData();
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: data
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/dicts/littlePca",
|
||||
method: "get",
|
||||
async handle() {
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: pcaDataLittle
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/tree/GetTreeChildrenByParentId",
|
||||
method: "get",
|
||||
async handle({ params }) {
|
||||
const list = await GetTreeChildrenByParentId(params.parentId);
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: list
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/mock/tree/GetNodesByValues",
|
||||
method: "get",
|
||||
async handle({ params }) {
|
||||
const list = await GetNodesByValues(params.values);
|
||||
return {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: list
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
|
@ -0,0 +1,70 @@
|
|||
export default [
|
||||
{
|
||||
code: "1",
|
||||
name: "北京",
|
||||
children: [
|
||||
{
|
||||
code: "2",
|
||||
name: "北京市区",
|
||||
children: [
|
||||
{
|
||||
code: "3",
|
||||
name: "海淀"
|
||||
},
|
||||
{
|
||||
code: "4",
|
||||
name: "朝阳"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
code: "5",
|
||||
name: "北京郊区",
|
||||
children: [
|
||||
{
|
||||
code: "6",
|
||||
name: "海淀郊区"
|
||||
},
|
||||
{
|
||||
code: "7",
|
||||
name: "朝阳郊区"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
code: "11",
|
||||
name: "深圳",
|
||||
children: [
|
||||
{
|
||||
code: "12",
|
||||
name: "深圳市区",
|
||||
children: [
|
||||
{
|
||||
code: "13",
|
||||
name: "南山"
|
||||
},
|
||||
{
|
||||
code: "14",
|
||||
name: "福田"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
code: "15",
|
||||
name: "深圳郊区",
|
||||
children: [
|
||||
{
|
||||
code: "16",
|
||||
name: "南山郊区"
|
||||
},
|
||||
{
|
||||
code: "17",
|
||||
name: "福田郊区"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
|
@ -0,0 +1,87 @@
|
|||
import _ from "lodash-es";
|
||||
export async function getPcasData() {
|
||||
const pcasData = () => import("china-division/dist/pcas-code.json");
|
||||
const ret = await pcasData();
|
||||
return ret.default;
|
||||
}
|
||||
export async function getPcaData() {
|
||||
const pcaData = () => import("china-division/dist/pca-code.json");
|
||||
const ret = await pcaData();
|
||||
return ret.default;
|
||||
}
|
||||
export const TreeNodesLazyLoader = {
|
||||
getNodesByValues(values) {
|
||||
console.log("getNodesByValues", values);
|
||||
if (!(values instanceof Array)) {
|
||||
values = [values];
|
||||
}
|
||||
return getPcasData().then((data) => {
|
||||
const nodes = [];
|
||||
for (const value of values) {
|
||||
const found = this.getNode(data, value);
|
||||
if (found) {
|
||||
const target = _.cloneDeep(found);
|
||||
delete target.children;
|
||||
nodes.push(target);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
});
|
||||
},
|
||||
getNode(list, value) {
|
||||
for (const item of list) {
|
||||
if (item.code === value) {
|
||||
return item;
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
const found = this.getNode(item.children, value);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getChildren(parent) {
|
||||
return getPcasData().then((data) => {
|
||||
const list = this.getChildrenByParent(parent, data);
|
||||
if (list == null) {
|
||||
return [];
|
||||
}
|
||||
return this.cloneAndDeleteChildren(list);
|
||||
});
|
||||
},
|
||||
getChildrenByParent(parentId, tree) {
|
||||
if (!parentId) {
|
||||
// 取第一级
|
||||
return tree;
|
||||
} else {
|
||||
for (const node of tree) {
|
||||
if (node.code === parentId) {
|
||||
return node.children;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
// 递归查找
|
||||
const list = this.getChildrenByParent(parentId, node.children);
|
||||
if (list) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
cloneAndDeleteChildren(list) {
|
||||
const newList = [];
|
||||
for (const node of list) {
|
||||
const newNode = {};
|
||||
Object.assign(newNode, node);
|
||||
if (newNode.children == null || newNode.children.length === 0) {
|
||||
newNode.isLeaf = true;
|
||||
newNode.leaf = true;
|
||||
}
|
||||
delete newNode.children;
|
||||
newList.push(newNode);
|
||||
}
|
||||
console.log("found children:", newList);
|
||||
return newList;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
import { mock } from "../api/service";
|
||||
import * as tools from "../api/tools";
|
||||
import _ from "lodash-es";
|
||||
const commonMocks = import.meta.globEager("./common/mock.*.js");
|
||||
const apiMocks = import.meta.globEager("../api/modules/*.mock.ts");
|
||||
const viewMocks = import.meta.globEager("../views/**/mock.js");
|
||||
|
||||
const list = [];
|
||||
_.forEach(commonMocks, (value) => {
|
||||
list.push(value.default);
|
||||
});
|
||||
_.forEach(apiMocks, (value) => {
|
||||
list.push(value.default);
|
||||
});
|
||||
_.forEach(viewMocks, (value) => {
|
||||
list.push(value.default);
|
||||
});
|
||||
|
||||
list.forEach((apiFile) => {
|
||||
for (const item of apiFile) {
|
||||
mock.onAny(new RegExp(item.path)).reply(async (config) => {
|
||||
console.log("------------fake request start -------------");
|
||||
console.log("request:", config);
|
||||
const data = config.data ? JSON.parse(config.data) : {};
|
||||
const query = config.url.indexOf("?") >= 0 ? config.url.substring(config.url.indexOf("?") + 1) : undefined;
|
||||
const params = config.params || {};
|
||||
if (query) {
|
||||
const arr = query.split("&");
|
||||
for (const item of arr) {
|
||||
const kv = item.split("=");
|
||||
params[kv[0]] = kv[1];
|
||||
}
|
||||
}
|
||||
|
||||
const req = {
|
||||
body: data,
|
||||
params: params
|
||||
};
|
||||
const ret = await item.handle(req);
|
||||
console.log("response:", ret);
|
||||
console.log("------------fake request end-------------");
|
||||
if (ret.code === 0) {
|
||||
return tools.responseSuccess(ret.data, ret.msg);
|
||||
} else {
|
||||
return tools.responseError(ret.data, ret.msg, ret.code);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,225 @@
|
|||
import { request, requestForMock } from "/src/api/service";
|
||||
import "/src/mock";
|
||||
import UiAntdv from "@fast-crud/ui-antdv";
|
||||
import { FastCrud, UseCrudProps, useTypes, setLogger } from "@fast-crud/fast-crud";
|
||||
import "@fast-crud/fast-crud/dist/style.css";
|
||||
import { FsExtendsUploader, FsExtendsEditor, FsExtendsJson, FsExtendsCopyable, FsExtendsTime } from "@fast-crud/fast-extends";
|
||||
import "@fast-crud/fast-extends/dist/style.css";
|
||||
|
||||
import { useCrudPermission } from "../permission";
|
||||
|
||||
function install(app, options: any = {}) {
|
||||
app.use(UiAntdv);
|
||||
//设置日志级别
|
||||
setLogger({ level: "debug" });
|
||||
app.use(FastCrud, {
|
||||
i18n: options.i18n,
|
||||
async dictRequest({ url }) {
|
||||
if (url && url.startsWith("/mock")) {
|
||||
//如果是crud开头的dict请求视为mock
|
||||
return await requestForMock({ url, method: "post" });
|
||||
}
|
||||
return await request({ url, method: "post" });
|
||||
},
|
||||
/**
|
||||
* useCrud时会被执行
|
||||
* @param context,useCrud的参数
|
||||
*/
|
||||
commonOptions(context: UseCrudProps) {
|
||||
const crudBinding = context.expose?.crudBinding;
|
||||
const opts = {
|
||||
table: {
|
||||
size: "small",
|
||||
pagination: false,
|
||||
onResizeColumn: (w, col) => {
|
||||
crudBinding.value.table.columnsMap[col.key].width = w;
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
buttons: {
|
||||
view: { type: "link", text: null, icon: "ion:eye-outline" },
|
||||
edit: { type: "link", text: null, icon: "ion:create-outline" },
|
||||
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" }
|
||||
},
|
||||
dropdown: {
|
||||
more: {
|
||||
type: "link"
|
||||
}
|
||||
}
|
||||
},
|
||||
request: {
|
||||
transformQuery: ({ page, form, sort }) => {
|
||||
const limit = page.pageSize;
|
||||
const currentPage = page.currentPage ?? 1;
|
||||
const offset = limit * (currentPage - 1);
|
||||
|
||||
sort = sort == null ? {} : sort;
|
||||
|
||||
return {
|
||||
page: {
|
||||
limit,
|
||||
offset
|
||||
},
|
||||
query: form,
|
||||
sort
|
||||
};
|
||||
},
|
||||
transformRes: ({ res }) => {
|
||||
const pageSize = res.limit;
|
||||
let currentPage = res.offset / pageSize;
|
||||
if (res.offset % pageSize === 0) {
|
||||
currentPage++;
|
||||
}
|
||||
return { currentPage, pageSize, ...res };
|
||||
}
|
||||
},
|
||||
form: {
|
||||
display: "flex",
|
||||
labelCol: {
|
||||
//固定label宽度
|
||||
span: null,
|
||||
style: {
|
||||
width: "120px"
|
||||
}
|
||||
},
|
||||
wrapperCol: {
|
||||
span: null
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 从 useCrud({permission}) 里获取permission参数,去设置各个按钮的权限
|
||||
const crudPermission = useCrudPermission({ permission: context.permission });
|
||||
return crudPermission.merge(opts);
|
||||
}
|
||||
});
|
||||
|
||||
// fast-extends里面的扩展组件均为异步组件,只有在使用时才会被加载,并不会影响首页加载速度
|
||||
//安装uploader 公共参数
|
||||
app.use(FsExtendsUploader, {
|
||||
defaultType: "cos",
|
||||
cos: {
|
||||
domain: "https://d2p-demo-1251260344.cos.ap-guangzhou.myqcloud.com",
|
||||
bucket: "d2p-demo-1251260344",
|
||||
region: "ap-guangzhou",
|
||||
secretId: "", //
|
||||
secretKey: "", // 传了secretKey 和secretId 代表使用本地签名模式(不安全,生产环境不推荐)
|
||||
getAuthorization(custom) {
|
||||
// 不传secretKey代表使用临时签名模式,此时此参数必传(安全,生产环境推荐)
|
||||
return request({
|
||||
url: "http://www.docmirror.cn:7070/api/upload/cos/getAuthorization",
|
||||
method: "get"
|
||||
}).then((ret) => {
|
||||
// 返回结构如下
|
||||
// ret.data:{
|
||||
// TmpSecretId,
|
||||
// TmpSecretKey,
|
||||
// XCosSecurityToken,
|
||||
// ExpiredTime, // SDK 在 ExpiredTime 时间前,不会再次调用 getAuthorization
|
||||
// }
|
||||
return ret;
|
||||
});
|
||||
},
|
||||
successHandle(ret) {
|
||||
// 上传完成后可以在此处处理结果,修改url什么的
|
||||
console.log("success handle:", ret);
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
alioss: {
|
||||
domain: "https://d2p-demo.oss-cn-shenzhen.aliyuncs.com",
|
||||
bucket: "d2p-demo",
|
||||
region: "oss-cn-shenzhen",
|
||||
accessKeyId: "",
|
||||
accessKeySecret: "",
|
||||
async getAuthorization(custom, context) {
|
||||
// 不传accessKeySecret代表使用临时签名模式,此时此参数必传(安全,生产环境推荐)
|
||||
const ret = await request({
|
||||
url: "http://www.docmirror.cn:7070/api/upload/alioss/getAuthorization",
|
||||
method: "get"
|
||||
});
|
||||
console.log("ret", ret);
|
||||
return ret;
|
||||
},
|
||||
sdkOpts: {
|
||||
// sdk配置
|
||||
secure: true // 默认为非https上传,为了安全,设置为true
|
||||
},
|
||||
successHandle(ret) {
|
||||
// 上传完成后可以在此处处理结果,修改url什么的
|
||||
console.log("success handle:", ret);
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
qiniu: {
|
||||
bucket: "d2p-demo",
|
||||
async getToken(options) {
|
||||
const ret = await request({
|
||||
url: "http://www.docmirror.cn:7070/api/upload/qiniu/getToken",
|
||||
method: "get"
|
||||
});
|
||||
return ret; // {token:xxx,expires:xxx}
|
||||
},
|
||||
successHandle(ret) {
|
||||
// 上传完成后可以在此处处理结果,修改url什么的
|
||||
console.log("success handle:", ret);
|
||||
return ret;
|
||||
},
|
||||
domain: "http://d2p.file.handsfree.work/"
|
||||
},
|
||||
form: {
|
||||
action: "http://www.docmirror.cn:7070/api/upload/form/upload",
|
||||
name: "file",
|
||||
withCredentials: false,
|
||||
uploadRequest: async ({ action, file, onProgress }) => {
|
||||
// @ts-ignore
|
||||
const data = new FormData();
|
||||
data.append("file", file);
|
||||
return await request({
|
||||
url: action,
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
},
|
||||
timeout: 60000,
|
||||
data,
|
||||
onUploadProgress: (p) => {
|
||||
onProgress({ percent: Math.round((p.loaded / p.total) * 100) });
|
||||
}
|
||||
});
|
||||
},
|
||||
successHandle(ret) {
|
||||
// 上传完成后的结果处理, 此处应返回格式为{url:xxx}
|
||||
return {
|
||||
url: "http://www.docmirror.cn:7070" + ret,
|
||||
key: ret.replace("/api/upload/form/download?key=", "")
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//安装editor
|
||||
app.use(FsExtendsEditor, {
|
||||
//编辑器的公共配置
|
||||
wangEditor: {}
|
||||
});
|
||||
app.use(FsExtendsJson);
|
||||
app.use(FsExtendsTime);
|
||||
app.use(FsExtendsCopyable);
|
||||
|
||||
const { addTypes } = useTypes();
|
||||
addTypes({
|
||||
time2: {
|
||||
//如果与官方字段类型同名,将会覆盖官方的字段类型
|
||||
form: { component: { name: "a-date-picker" } },
|
||||
column: { component: { name: "fs-date-format", format: "YYYY-MM-DD" } },
|
||||
valueBuilder(context) {
|
||||
console.log("time2,valueBuilder", context);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
import "./iconfont.js"
|
|
@ -0,0 +1,2 @@
|
|||
// import "@iconify/iconify";
|
||||
import "@purge-icons/generated";
|
|
@ -0,0 +1,12 @@
|
|||
import "./iconify";
|
||||
import "./iconfont";
|
||||
import FastCrud from "./fast-crud";
|
||||
import permission from "./permission";
|
||||
function install(app, options: any = {}) {
|
||||
app.use(FastCrud, options);
|
||||
app.use(permission);
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
import { request } from "/src/api/service";
|
||||
export async function getPermissions() {
|
||||
const ret = await request({
|
||||
url: "/sys/authority/user/permissions",
|
||||
method: "post"
|
||||
});
|
||||
// 如果使用你自己的后端,需要在此处将返回结果改造为本模块需要的结构
|
||||
// 结构详情,请参考示例中打印的日志 ”获取权限数据成功:{...}“ (实际上就是“资源管理”页面中列出来的数据)
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import permission from "./permission";
|
||||
import permissionUtil from "../util.permission";
|
||||
const install = function (app) {
|
||||
app.directive("permission", permission);
|
||||
app.config.globalProperties.$hasPermissions = permissionUtil.hasPermissions;
|
||||
};
|
||||
|
||||
permission.install = install;
|
||||
export default permission;
|
|
@ -0,0 +1,11 @@
|
|||
import permissionUtil from "../util.permission";
|
||||
export default {
|
||||
mounted(el, binding, vnode) {
|
||||
const { value } = binding;
|
||||
const hasPermission = permissionUtil.hasPermissions(value);
|
||||
|
||||
if (!hasPermission) {
|
||||
el.parentNode && el.parentNode.removeChild(el);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export class NoPermissionError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message || "对不起,您没有权限执行此操作");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import router from "/src/router";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import { usePermissionStore } from "./store.permission";
|
||||
import util from "./util.permission";
|
||||
import { message } from "ant-design-vue";
|
||||
import NProgress from "nprogress";
|
||||
export function registerRouterHook() {
|
||||
// 注册路由beforeEach钩子,在第一次加载路由页面时,加载权限
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const permissionStore = usePermissionStore();
|
||||
if (permissionStore.isInited) {
|
||||
if (to.meta.permission) {
|
||||
//校验权限
|
||||
// @ts-ignore
|
||||
if (!util.hasPermissions(to.meta.permission)) {
|
||||
//没有权限
|
||||
message.warn("对不起,您没有权限");
|
||||
//throw new Error("对不起,您没有权限");
|
||||
NProgress.done();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
const token = userStore.getToken;
|
||||
if (!token || token === "undefined") {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化权限列表
|
||||
try {
|
||||
console.log("permission is enabled");
|
||||
await permissionStore.loadFromRemote();
|
||||
console.log("PM load success");
|
||||
next({ ...to, replace: true });
|
||||
} catch (e) {
|
||||
console.error("加载动态路由失败", e);
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import permissionDirective from "./directive/index";
|
||||
import { registerRouterHook } from "./hook";
|
||||
import util from "./util.permission";
|
||||
export * from "./use-crud-permission";
|
||||
export * from "./errors";
|
||||
|
||||
export function usePermission() {
|
||||
return {
|
||||
...util
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
// 开启权限模块
|
||||
// 注册v-permission指令, 用于控制按钮权限
|
||||
app.use(permissionDirective);
|
||||
// 注册路由钩子
|
||||
// 通过路由守卫,在登录成功后拦截路由,从后台加载权限数据
|
||||
// 然后将权限数据转化为菜单和路由,添加到系统中
|
||||
registerRouterHook();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { useResourceStore } from "/src/store/modules/resource";
|
||||
import { getPermissions } from "./api";
|
||||
import { mitter } from "/@/utils/util.mitt";
|
||||
import { env } from "/@/utils/util.env";
|
||||
|
||||
//监听注销事件
|
||||
mitter.on("app.logout", () => {
|
||||
const permissionStore = usePermissionStore();
|
||||
permissionStore.clear();
|
||||
});
|
||||
|
||||
interface PermissionState {
|
||||
permissions: [];
|
||||
inited: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建权限码列表
|
||||
* @param menuTree
|
||||
* @param permissionList
|
||||
* @returns {*}
|
||||
*/
|
||||
function formatPermissions(menuTree: Array<any>, permissionList = []) {
|
||||
if (menuTree == null) {
|
||||
menuTree = [];
|
||||
}
|
||||
menuTree.forEach((item: any) => {
|
||||
if (item.permission) {
|
||||
// @ts-ignore
|
||||
permissionList.push(item.permission);
|
||||
}
|
||||
if (item.children != null && item.children.length > 0) {
|
||||
formatPermissions(item.children, permissionList);
|
||||
}
|
||||
});
|
||||
return permissionList;
|
||||
}
|
||||
|
||||
export const usePermissionStore = defineStore({
|
||||
id: "app.permission",
|
||||
state: (): PermissionState => ({
|
||||
permissions: [],
|
||||
inited: false
|
||||
}),
|
||||
getters: {
|
||||
getPermissions() {
|
||||
// @ts-ignore
|
||||
return this.permissions;
|
||||
},
|
||||
isInited() {
|
||||
// @ts-ignore
|
||||
return this.inited;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
init({ permissions }) {
|
||||
this.permissions = permissions;
|
||||
this.inited = true;
|
||||
},
|
||||
clear() {
|
||||
this.permissions = [];
|
||||
this.inited = false;
|
||||
},
|
||||
resolve(resourceTree) {
|
||||
const permissions = formatPermissions(resourceTree);
|
||||
this.init({ permissions });
|
||||
|
||||
//过滤没有权限的菜单
|
||||
const resourceStore = useResourceStore();
|
||||
resourceStore.filterByPermission(permissions);
|
||||
},
|
||||
async loadFromRemote() {
|
||||
let permissionTree = [];
|
||||
if (env.PM_ENABLED === "false") {
|
||||
console.warn("当前权限模块未开启,权限列表为空");
|
||||
} else {
|
||||
//开启了权限模块,向后台请求权限列表
|
||||
const data = await getPermissions();
|
||||
if (data != null) {
|
||||
permissionTree = data;
|
||||
} else {
|
||||
console.warn("当前获取到的权限列表为空");
|
||||
}
|
||||
}
|
||||
this.resolve(permissionTree);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
import { usePermission } from "/@/plugin/permission";
|
||||
import _ from "lodash-es";
|
||||
|
||||
/**
|
||||
* 设置按钮动作权限
|
||||
* @param permission {prefix,extra}
|
||||
*/
|
||||
export function useCrudPermission({ permission }) {
|
||||
const { hasPermissions } = usePermission();
|
||||
|
||||
const prefix = permission instanceof Object ? permission.prefix : permission;
|
||||
|
||||
//根据权限显示按钮
|
||||
function hasActionPermission(action) {
|
||||
if (!prefix) {
|
||||
return true;
|
||||
}
|
||||
return hasPermissions(prefix + ":" + action);
|
||||
}
|
||||
|
||||
function buildCrudPermission() {
|
||||
if (permission == null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let extra = {};
|
||||
if (permission instanceof Object) {
|
||||
extra = permission.extra;
|
||||
if (permission.extra && permission.extra instanceof Function) {
|
||||
extra = permission.extra({ hasActionPermission });
|
||||
}
|
||||
}
|
||||
|
||||
return _.merge(
|
||||
{
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: { show: hasActionPermission("add") }
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
buttons: {
|
||||
edit: { show: hasActionPermission("edit") },
|
||||
remove: { show: hasActionPermission("remove") },
|
||||
view: { show: hasActionPermission("view") }
|
||||
}
|
||||
}
|
||||
},
|
||||
extra
|
||||
);
|
||||
}
|
||||
|
||||
function merge(userOptions) {
|
||||
const permissionOptions = buildCrudPermission();
|
||||
_.merge(permissionOptions, userOptions);
|
||||
return permissionOptions;
|
||||
}
|
||||
|
||||
return { merge, buildCrudPermission, hasActionPermission };
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { usePermissionStore } from "./store.permission";
|
||||
import { NoPermissionError } from "./errors";
|
||||
import { message } from "ant-design-vue";
|
||||
const util = {
|
||||
hasPermissions: (value: string | string[]): boolean => {
|
||||
let need: string[] = [];
|
||||
if (typeof value === "string") {
|
||||
need.push(value);
|
||||
} else if (value && value instanceof Array && value.length > 0) {
|
||||
need = need.concat(value);
|
||||
}
|
||||
if (need.length === 0) {
|
||||
throw new Error('need permissions! Like "sys:user:view" ');
|
||||
}
|
||||
const permissionStore = usePermissionStore();
|
||||
const userPermissionList = permissionStore.getPermissions;
|
||||
return userPermissionList.some((permission) => {
|
||||
return need.includes(permission);
|
||||
});
|
||||
},
|
||||
requirePermissions: (value) => {
|
||||
if (!util.hasPermissions(value)) {
|
||||
message.error("对不起,您没有权限执行此操作");
|
||||
throw new NoPermissionError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default util;
|
|
@ -0,0 +1,69 @@
|
|||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
// 进度条
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
import { usePageStore } from "../store/modules/page";
|
||||
import { site } from "../utils/util.site";
|
||||
import { routes } from "./resolve";
|
||||
import { useResourceStore } from "../store/modules/resource";
|
||||
import { useUserStore } from "../store/modules/user";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
/**
|
||||
* 路由拦截
|
||||
*/
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// 进度条
|
||||
NProgress.start();
|
||||
|
||||
// 验证当前路由所有的匹配中是否需要有登录验证的
|
||||
if (
|
||||
to.matched.some((r) => {
|
||||
return r.meta?.auth || r.meta?.permission;
|
||||
})
|
||||
) {
|
||||
const userStore = useUserStore();
|
||||
// 这里暂时将cookie里是否存有token作为验证是否登录的条件
|
||||
// 请根据自身业务需要修改
|
||||
const token = userStore.getToken;
|
||||
if (token) {
|
||||
next();
|
||||
} else {
|
||||
// 没有登录的时候跳转到登录界面
|
||||
// 携带上登陆成功之后需要跳转的页面完整路径
|
||||
next({
|
||||
name: "login",
|
||||
query: {
|
||||
redirect: to.fullPath
|
||||
}
|
||||
});
|
||||
// https://github.com/d2-projects/d2-admin/issues/138
|
||||
NProgress.done();
|
||||
}
|
||||
} else {
|
||||
// 不需要身份校验 直接通过
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach((to) => {
|
||||
// 进度条
|
||||
NProgress.done();
|
||||
// 多页控制 打开新的页面
|
||||
const pageStore = usePageStore();
|
||||
pageStore.open(to);
|
||||
// 更改标题
|
||||
site.title(to.meta.title);
|
||||
|
||||
//修改左侧边栏
|
||||
const matched = to.matched;
|
||||
if (matched.length > 0) {
|
||||
const resourceStore = useResourceStore();
|
||||
resourceStore.setAsideMenuByCurrentRoute(matched);
|
||||
}
|
||||
});
|
||||
export default router;
|
|
@ -0,0 +1,155 @@
|
|||
import LayoutPass from "/src/layout/layout-pass.vue";
|
||||
import _ from "lodash-es";
|
||||
import { outsideResource } from "./source/outside";
|
||||
import { headerResource } from "./source/header";
|
||||
import { frameworkResource } from "./source/framework";
|
||||
// @ts-ignore
|
||||
const modules = import.meta.glob("/src/views/**/*.vue");
|
||||
|
||||
let index = 0;
|
||||
function transformOneResource(resource) {
|
||||
let menu: any = null;
|
||||
if (resource.meta == null) {
|
||||
resource.meta = {};
|
||||
}
|
||||
const meta = resource.meta;
|
||||
meta.title = meta.title ?? resource.title ?? "未命名";
|
||||
if (resource.title == null) {
|
||||
resource.title = meta.title;
|
||||
}
|
||||
if (meta.isMenu === false) {
|
||||
menu = null;
|
||||
} else {
|
||||
menu = _.cloneDeep(resource);
|
||||
delete menu.component;
|
||||
}
|
||||
let route;
|
||||
if (resource.type !== "menu") {
|
||||
if (resource.path == null || resource.path.startsWith("https://") || resource.path.startsWith("http://")) {
|
||||
//没有route
|
||||
route = null;
|
||||
} else {
|
||||
route = _.cloneDeep(resource);
|
||||
if (route.component && typeof route.component === "string") {
|
||||
const path = "/src/views" + route.component;
|
||||
route.component = modules[path];
|
||||
}
|
||||
if (route.component == null) {
|
||||
route.component = LayoutPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
menu,
|
||||
route
|
||||
};
|
||||
}
|
||||
|
||||
export const buildMenusAndRouters = (resources) => {
|
||||
const routes: Array<any> = [];
|
||||
const menus: Array<any> = [];
|
||||
for (const item of resources) {
|
||||
const { menu, route } = transformOneResource(item);
|
||||
let menuChildren;
|
||||
let routeChildren;
|
||||
if (item.children) {
|
||||
if (item.children.length > 0) {
|
||||
const ret = buildMenusAndRouters(item.children);
|
||||
menuChildren = ret.menus;
|
||||
routeChildren = ret.routes;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu) {
|
||||
menus.push(menu);
|
||||
menu.children = menuChildren;
|
||||
}
|
||||
if (route) {
|
||||
if (route?.meta?.cache !== false) {
|
||||
if (route.meta == null) {
|
||||
route.meta = {};
|
||||
}
|
||||
route.meta.cache = true;
|
||||
}
|
||||
routes.push(route);
|
||||
route.children = routeChildren;
|
||||
}
|
||||
}
|
||||
|
||||
setIndex(menus);
|
||||
return {
|
||||
routes,
|
||||
menus
|
||||
};
|
||||
};
|
||||
|
||||
function setIndex(menus) {
|
||||
for (const menu of menus) {
|
||||
menu.index = "index_" + index;
|
||||
index++;
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
setIndex(menu.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findMenus(menus, condition) {
|
||||
const list: any = [];
|
||||
for (const menu of menus) {
|
||||
if (condition(menu)) {
|
||||
list.push(menu);
|
||||
}
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const subList = findMenus(menu.children, condition);
|
||||
for (const item of subList) {
|
||||
list.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
function filterMenus(menus, condition) {
|
||||
const list = menus.filter((item) => {
|
||||
return condition(item);
|
||||
});
|
||||
|
||||
for (const item of list) {
|
||||
if (item.children && item.children.length > 0) {
|
||||
item.children = filterMenus(item.children, condition);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
function flatChildren(list, children) {
|
||||
for (const child of children) {
|
||||
list.push(child);
|
||||
if (child.children && child.children.length > 0) {
|
||||
flatChildren(list, child.children);
|
||||
}
|
||||
child.children = null;
|
||||
}
|
||||
}
|
||||
function flatSubRouters(routers) {
|
||||
for (const router of routers) {
|
||||
const children: Array<any> = [];
|
||||
if (router.children && router.children.length > 0) {
|
||||
flatChildren(children, router.children);
|
||||
}
|
||||
router.children = children;
|
||||
}
|
||||
return routers;
|
||||
}
|
||||
|
||||
const frameworkRet = buildMenusAndRouters(frameworkResource);
|
||||
const outsideRet = buildMenusAndRouters(outsideResource);
|
||||
const headerRet = buildMenusAndRouters(headerResource);
|
||||
|
||||
const outsideRoutes = outsideRet.routes;
|
||||
const frameworkRoutes = flatSubRouters(frameworkRet.routes);
|
||||
const routes = [...outsideRoutes, ...frameworkRoutes];
|
||||
const frameworkMenus = frameworkRet.menus;
|
||||
const headerMenus = headerRet.menus;
|
||||
export { routes, outsideRoutes, frameworkRoutes, frameworkMenus, headerMenus, findMenus, filterMenus };
|
|
@ -0,0 +1,35 @@
|
|||
import LayoutFramework from "/src/layout/layout-framework.vue";
|
||||
//import { crudResources } from "/@/router/source/modules/crud";
|
||||
import { sysResources } from "/@/router/source/modules/sys";
|
||||
import { certdResources } from "/@/router/source/modules/certd";
|
||||
|
||||
export const frameworkResource = [
|
||||
{
|
||||
title: "框架",
|
||||
name: "framework",
|
||||
path: "/",
|
||||
redirect: "/index",
|
||||
component: LayoutFramework,
|
||||
meta: {
|
||||
icon: "ion:accessibility",
|
||||
auth: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "首页",
|
||||
name: "index",
|
||||
path: "/index",
|
||||
component: "/framework/home/index.vue",
|
||||
meta: {
|
||||
fixedAside: true,
|
||||
showOnHeader: false,
|
||||
icon: "ion:home-outline"
|
||||
}
|
||||
},
|
||||
//...crudResources,
|
||||
...certdResources,
|
||||
...sysResources
|
||||
]
|
||||
}
|
||||
];
|
||||
console.assert(frameworkResource.length === 1, "frameworkResource数组长度只能为1,你只能配置framework路由的子路由");
|
|
@ -0,0 +1,73 @@
|
|||
export const headerResource = [
|
||||
{
|
||||
title: "文档",
|
||||
path: "http://fast-crud.docmirror.cn/"
|
||||
},
|
||||
{
|
||||
title: "其他Demo",
|
||||
name: "demo",
|
||||
children: [
|
||||
{
|
||||
title: "Element版",
|
||||
path: "http://fast-crud.docmirror.cn/element/"
|
||||
},
|
||||
{
|
||||
title: "VbenAdmin",
|
||||
path: "http://fast-crud.docmirror.cn/vben/"
|
||||
},
|
||||
{
|
||||
title: "cool-admin-vue",
|
||||
path: "http://fast-crud.docmirror.cn/cool/"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: "源码",
|
||||
name: "source",
|
||||
key: "source",
|
||||
meta: {
|
||||
icon: "ion:git-branch-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "fast-crud",
|
||||
children: [
|
||||
{
|
||||
title: "github",
|
||||
path: "http://github.com/fast-crud/fast-crud",
|
||||
meta: {
|
||||
icon: "ion:logo-github"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "gitee",
|
||||
path: "http://gitee.com/fast-crud/fast-crud",
|
||||
meta: {
|
||||
icon: "ion:logo-octocat"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "fs-admin",
|
||||
children: [
|
||||
{
|
||||
title: "github",
|
||||
path: "http://github.com/fast-crud/fs-admin-antdv",
|
||||
meta: {
|
||||
icon: "ion:logo-github"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "gitee",
|
||||
path: "http://gitee.com/fast-crud/fs-admin-antdv",
|
||||
meta: {
|
||||
icon: "ion:logo-octocat"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
|
@ -0,0 +1,41 @@
|
|||
export const certdResources = [
|
||||
{
|
||||
title: "证书自动化",
|
||||
name: "certd",
|
||||
path: "/certd",
|
||||
redirect: "/certd/pipeline",
|
||||
meta: {
|
||||
icon: "ion:key-outline",
|
||||
auth: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "证书自动化流水线",
|
||||
name: "pipeline",
|
||||
path: "/certd/pipeline",
|
||||
component: "/certd/pipeline/index.vue",
|
||||
meta: {
|
||||
icon: "ion:analytics-sharp"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "编辑流水线",
|
||||
name: "pipelineEdit",
|
||||
path: "/certd/pipeline/detail",
|
||||
component: "/certd/pipeline/detail.vue",
|
||||
meta: {
|
||||
isMenu: false
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "授权管理",
|
||||
name: "access",
|
||||
path: "/certd/access",
|
||||
component: "/certd/access/index.vue",
|
||||
meta: {
|
||||
icon: "ion:disc-outline"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
|
@ -0,0 +1,604 @@
|
|||
export const crudResources = [
|
||||
{
|
||||
title: "CRUD示例",
|
||||
name: "crud",
|
||||
path: "/crud",
|
||||
redirect: "/crud/basis",
|
||||
meta: {
|
||||
icon: "ion:apps-sharp"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "基本特性",
|
||||
name: "basis",
|
||||
path: "/crud/basis",
|
||||
redirect: "/crud/basis/i18n",
|
||||
meta: {
|
||||
icon: "ion:disc-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "HelloWorld",
|
||||
name: "FsCrudFirst",
|
||||
path: "/crud/basis/first",
|
||||
component: "/crud/basis/first/index.vue"
|
||||
},
|
||||
{
|
||||
title: "动态计算",
|
||||
name: "BasisCompute",
|
||||
path: "/crud/basis/compute",
|
||||
component: "/crud/basis/compute/index.vue"
|
||||
},
|
||||
{
|
||||
title: "动态计算-更多示例",
|
||||
name: "BasisComputeMore",
|
||||
path: "/crud/basis/compute-more",
|
||||
component: "/crud/basis/compute-more/index.vue"
|
||||
},
|
||||
{
|
||||
title: "国际化",
|
||||
name: "BasisI18n",
|
||||
path: "/crud/basis/i18n",
|
||||
component: "/crud/basis/i18n/index.vue"
|
||||
},
|
||||
{
|
||||
title: "ValueChange",
|
||||
name: "BasisValueChange",
|
||||
path: "/crud/basis/value-change",
|
||||
component: "/crud/basis/value-change/index.vue"
|
||||
},
|
||||
{
|
||||
title: "Card布局",
|
||||
name: "BasisLayoutCard",
|
||||
path: "/crud/basis/layout-card",
|
||||
component: "/crud/basis/layout-card/index.vue"
|
||||
},
|
||||
{
|
||||
title: "自定义布局",
|
||||
name: "BasisLayoutCustom",
|
||||
path: "/crud/basis/layout-custom",
|
||||
component: "/crud/basis/layout-custom/index.vue"
|
||||
},
|
||||
{
|
||||
title: "列设置",
|
||||
name: "BasisColumnsSet",
|
||||
path: "/crud/basis/columns-set",
|
||||
component: "/crud/basis/columns-set/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "数据字典",
|
||||
name: "dict",
|
||||
path: "/crud/dict",
|
||||
redirect: "/crud/dict/single",
|
||||
meta: {
|
||||
icon: "ion:book-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "单例",
|
||||
name: "DictSingle",
|
||||
path: "/crud/dict/single",
|
||||
component: "/crud/dict/single/index.vue"
|
||||
},
|
||||
{
|
||||
title: "分发复制",
|
||||
name: "DictCloneable",
|
||||
path: "/crud/dict/cloneable",
|
||||
component: "/crud/dict/cloneable/index.vue"
|
||||
},
|
||||
{
|
||||
title: "原型复制",
|
||||
name: "DictPrototype",
|
||||
path: "/crud/dict/prototype",
|
||||
component: "/crud/dict/prototype/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "操作列",
|
||||
name: "row-handle",
|
||||
path: "/crud/row-handle",
|
||||
redirect: "/crud/row-handle/tooltip",
|
||||
meta: {
|
||||
icon: "ion:build-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Tooltip",
|
||||
name: "RowHandleTooltip",
|
||||
path: "/crud/row-handle/tooltip",
|
||||
component: "/crud/row-handle/tooltip/index.vue"
|
||||
},
|
||||
{
|
||||
title: "按钮折叠",
|
||||
name: "RowHandleDropdown",
|
||||
path: "/crud/row-handle/dropdown",
|
||||
component: "/crud/row-handle/dropdown/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "组件示例",
|
||||
name: "component",
|
||||
path: "/crud/component",
|
||||
redirect: "/crud/component/text",
|
||||
meta: {
|
||||
icon: "ion:cube-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "文本输入(input)",
|
||||
name: "ComponentText",
|
||||
path: "/crud/component/text",
|
||||
component: "/crud/component/text/index.vue"
|
||||
},
|
||||
{
|
||||
title: "选择(select)",
|
||||
name: "ComponentSelect",
|
||||
path: "/crud/component/select",
|
||||
component: "/crud/component/select/index.vue"
|
||||
},
|
||||
{
|
||||
title: "级联(cascader)",
|
||||
name: "ComponentCascader",
|
||||
path: "/crud/component/cascader",
|
||||
component: "/crud/component/cascader/index.vue"
|
||||
},
|
||||
{
|
||||
title: "多选(checkbox)",
|
||||
name: "ComponentCheckbox",
|
||||
path: "/crud/component/checkbox",
|
||||
component: "/crud/component/checkbox/index.vue"
|
||||
},
|
||||
{
|
||||
title: "单选(radio)",
|
||||
name: "ComponentRadio",
|
||||
path: "/crud/component/radio",
|
||||
component: "/crud/component/radio/index.vue"
|
||||
},
|
||||
{
|
||||
title: "开关(switch)",
|
||||
name: "ComponentSwitch",
|
||||
path: "/crud/component/switch",
|
||||
component: "/crud/component/switch/index.vue"
|
||||
},
|
||||
{
|
||||
title: "日期时间(date)",
|
||||
name: "ComponentDate",
|
||||
path: "/crud/component/date",
|
||||
component: "/crud/component/date/index.vue"
|
||||
},
|
||||
{
|
||||
title: "按钮链接",
|
||||
name: "ComponentButton",
|
||||
path: "/crud/component/button",
|
||||
component: "/crud/component/button/index.vue"
|
||||
},
|
||||
{
|
||||
title: "数字",
|
||||
name: "ComponentNumber",
|
||||
path: "/crud/component/number",
|
||||
component: "/crud/component/number/index.vue"
|
||||
},
|
||||
{
|
||||
title: "树形选择",
|
||||
name: "ComponentTree",
|
||||
path: "/crud/component/tree",
|
||||
component: "/crud/component/tree/index.vue"
|
||||
},
|
||||
{
|
||||
title: "图片裁剪上传",
|
||||
name: "ComponentUploaderCropper",
|
||||
path: "/crud/component/uploader/cropper",
|
||||
component: "/crud/component/uploader/cropper/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单本地上传",
|
||||
name: "ComponentUploaderForm",
|
||||
path: "/crud/component/uploader/form",
|
||||
component: "/crud/component/uploader/form/index.vue"
|
||||
},
|
||||
{
|
||||
title: "阿里云oss上传",
|
||||
name: "ComponentUploaderAlioss",
|
||||
path: "/crud/component/uploader/alioss",
|
||||
component: "/crud/component/uploader/alioss/index.vue"
|
||||
},
|
||||
{
|
||||
title: "腾讯云cos上传",
|
||||
name: "ComponentUploaderCos",
|
||||
path: "/crud/component/uploader/cos",
|
||||
component: "/crud/component/uploader/cos/index.vue"
|
||||
},
|
||||
{
|
||||
title: "七牛云上传",
|
||||
name: "ComponentUploaderQiniu",
|
||||
path: "/crud/component/uploader/qiniu",
|
||||
component: "/crud/component/uploader/qiniu/index.vue"
|
||||
},
|
||||
{
|
||||
title: "富文本编辑器",
|
||||
name: "ComponentEditor",
|
||||
path: "/crud/component/editor",
|
||||
component: "/crud/component/editor/index.vue"
|
||||
},
|
||||
{
|
||||
title: "图标",
|
||||
name: "ComponentIcon",
|
||||
path: "/crud/component/icon",
|
||||
component: "/crud/component/icon/index.vue"
|
||||
},
|
||||
{
|
||||
title: "JsonEditor",
|
||||
name: "ComponentJson",
|
||||
path: "/crud/component/json",
|
||||
component: "/crud/component/json/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Form表单",
|
||||
name: "form",
|
||||
path: "/crud/form",
|
||||
redirect: "/crud/form/layout",
|
||||
meta: {
|
||||
icon: "ion:document-text-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "基本表单",
|
||||
name: "FormBase",
|
||||
path: "/crud/form/base",
|
||||
component: "/crud/form/base/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单Grid布局",
|
||||
name: "FormLayoutGrid",
|
||||
path: "/crud/form/layout-grid",
|
||||
component: "/crud/form/layout-grid/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单Flex布局",
|
||||
name: "FormLayoutFlex",
|
||||
path: "/crud/form/layout-flex",
|
||||
component: "/crud/form/layout-flex/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单动态布局",
|
||||
name: "FormLayout",
|
||||
path: "/crud/form/layout",
|
||||
component: "/crud/form/layout/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单校验",
|
||||
name: "FormValidation",
|
||||
path: "/crud/form/validation",
|
||||
component: "/crud/form/validation/index.vue"
|
||||
},
|
||||
{
|
||||
title: "抽屉表单",
|
||||
name: "FormDrawer",
|
||||
path: "/crud/form/drawer",
|
||||
component: "/crud/form/drawer/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单分组",
|
||||
name: "FormGroup",
|
||||
path: "/crud/form/group",
|
||||
component: "/crud/form/group/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单分组(tabs)",
|
||||
name: "FormGroupTabs",
|
||||
path: "/crud/form/group-tabs",
|
||||
component: "/crud/form/group-tabs/index.vue"
|
||||
},
|
||||
{
|
||||
title: "自定义表单",
|
||||
name: "FormCustomForm",
|
||||
path: "/crud/form/custom-form",
|
||||
component: "/crud/form/custom-form/index.vue"
|
||||
},
|
||||
{
|
||||
title: "字段帮助说明",
|
||||
name: "FormHelper",
|
||||
path: "/crud/form/helper",
|
||||
component: "/crud/form/helper/index.vue"
|
||||
},
|
||||
{
|
||||
title: "页面内部弹出表单",
|
||||
name: "FormInner",
|
||||
path: "/crud/form/inner",
|
||||
component: "/crud/form/inner/index.vue",
|
||||
meta: {
|
||||
cache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "地区字典管理",
|
||||
name: "FormInnerArea",
|
||||
path: "/crud/form/inner/area",
|
||||
component: "/crud/form/inner/area/index.vue",
|
||||
meta: {
|
||||
isMenu: false
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "新页面编辑",
|
||||
name: "FormNewPage",
|
||||
path: "/crud/form/new-page",
|
||||
component: "/crud/form/new-page/index.vue",
|
||||
meta: {
|
||||
cache: false
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "新页面编辑表单",
|
||||
name: "FormNewPageEdit",
|
||||
path: "/crud/form/new-page/edit",
|
||||
component: "/crud/form/new-page/edit.vue",
|
||||
meta: {
|
||||
isMenu: false
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "独立使用表单",
|
||||
name: "FormIndependent",
|
||||
path: "/crud/form/independent",
|
||||
component: "/crud/form/independent/index.vue"
|
||||
},
|
||||
{
|
||||
title: "重置表单",
|
||||
name: "FormReset",
|
||||
path: "/crud/form/reset",
|
||||
component: "/crud/form/reset/index.vue"
|
||||
},
|
||||
{
|
||||
title: "嵌套数据结构",
|
||||
name: "FormNest",
|
||||
path: "/crud/form/nest",
|
||||
component: "/crud/form/nest/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "表格特性",
|
||||
path: "/crud/feature",
|
||||
meta: {
|
||||
icon: "ion:beer-outline"
|
||||
},
|
||||
redirect: "/crud/feature/dropdown",
|
||||
children: [
|
||||
{
|
||||
title: "部件显隐",
|
||||
name: "FeatureHide",
|
||||
path: "/crud/feature/hide",
|
||||
component: "/crud/feature/hide/index.vue"
|
||||
},
|
||||
{
|
||||
title: "多选&批量删除",
|
||||
name: "FeatureSelection",
|
||||
path: "/crud/feature/selection",
|
||||
component: "/crud/feature/selection/index.vue"
|
||||
},
|
||||
{
|
||||
title: "单选",
|
||||
name: "FeatureSelectionRadio",
|
||||
path: "/crud/feature/selection-radio",
|
||||
component: "/crud/feature/selection-radio/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表头过滤",
|
||||
name: "FeatureFilter",
|
||||
path: "/crud/feature/filter",
|
||||
component: "/crud/feature/filter/index.vue"
|
||||
},
|
||||
{
|
||||
title: "行展开",
|
||||
name: "FeatureExpand",
|
||||
path: "/crud/feature/expand",
|
||||
component: "/crud/feature/expand/index.vue"
|
||||
},
|
||||
{
|
||||
title: "树形表格",
|
||||
name: "FeatureTree",
|
||||
path: "/crud/feature/tree",
|
||||
component: "/crud/feature/tree/index.vue"
|
||||
},
|
||||
{
|
||||
title: "多级表头",
|
||||
name: "FeatureHeaderGroup",
|
||||
path: "/crud/feature/header-group",
|
||||
component: "/crud/feature/header-group/index.vue"
|
||||
},
|
||||
{
|
||||
title: "合并单元格",
|
||||
name: "FeatureMerge",
|
||||
path: "/crud/feature/merge",
|
||||
component: "/crud/feature/merge/index.vue"
|
||||
},
|
||||
{
|
||||
title: "序号",
|
||||
name: "FeatureIndex",
|
||||
path: "/crud/feature/index",
|
||||
component: "/crud/feature/index/index.vue"
|
||||
},
|
||||
{
|
||||
title: "排序",
|
||||
name: "FeatureSortable",
|
||||
path: "/crud/feature/sortable",
|
||||
component: "/crud/feature/sortable/index.vue"
|
||||
},
|
||||
{
|
||||
title: "固定列",
|
||||
name: "FeatureFixed",
|
||||
path: "/crud/feature/fixed",
|
||||
component: "/crud/feature/fixed/index.vue"
|
||||
},
|
||||
{
|
||||
title: "不固定高度",
|
||||
name: "FeatureHeight",
|
||||
path: "/crud/feature/height",
|
||||
component: "/crud/feature/height/index.vue"
|
||||
},
|
||||
{
|
||||
title: "可编辑",
|
||||
name: "FeatureEditable",
|
||||
path: "/crud/feature/editable",
|
||||
component: "/crud/feature/editable/index.vue"
|
||||
},
|
||||
{
|
||||
title: "行编辑",
|
||||
name: "FeatureEditableRow",
|
||||
path: "/crud/feature/editable-row",
|
||||
component: "/crud/feature/editable-row/index.vue"
|
||||
},
|
||||
{
|
||||
title: "查询框",
|
||||
name: "FeatureSearch",
|
||||
path: "/crud/feature/search",
|
||||
component: "/crud/feature/search/index.vue"
|
||||
},
|
||||
{
|
||||
title: "查询框多行模式",
|
||||
name: "FeatureSearchMulti",
|
||||
path: "/crud/feature/search-multi",
|
||||
component: "/crud/feature/search-multi/index.vue"
|
||||
},
|
||||
{
|
||||
title: "字段排序",
|
||||
name: "FeatureColumnSort",
|
||||
path: "/crud/feature/column-sort",
|
||||
component: "/crud/feature/column-sort/index.vue"
|
||||
},
|
||||
{
|
||||
title: "ValueBuilder",
|
||||
name: "FeatureValueBuilder",
|
||||
path: "/crud/feature/value-builder",
|
||||
component: "/crud/feature/value-builder/index.vue"
|
||||
},
|
||||
{
|
||||
title: "列设置",
|
||||
name: "FeatureColumnsSet",
|
||||
path: "/crud/feature/columns-set",
|
||||
component: "/crud/feature/columns-set/index.vue"
|
||||
},
|
||||
{
|
||||
title: "本地化编辑",
|
||||
name: "FeatureLocal",
|
||||
path: "/crud/feature/local",
|
||||
component: "/crud/feature/local/index.vue"
|
||||
},
|
||||
{
|
||||
title: "v-model",
|
||||
name: "FeatureVModel",
|
||||
path: "/crud/feature/v-model",
|
||||
component: "/crud/feature/local-v-model/index.vue"
|
||||
},
|
||||
{
|
||||
title: "自定义删除",
|
||||
name: "FeatureRemove",
|
||||
path: "/crud/feature/remove",
|
||||
component: "/crud/feature/remove/index.vue"
|
||||
},
|
||||
{
|
||||
title: "调整列宽",
|
||||
name: "FeatureColumnResize",
|
||||
path: "/crud/feature/column-resize",
|
||||
component: "/crud/feature/column-resize/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "插槽",
|
||||
name: "Slots",
|
||||
path: "/crud/slots",
|
||||
redirect: "/crud/slots/layout",
|
||||
meta: {
|
||||
icon: "ion:extension-puzzle-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "页面占位插槽",
|
||||
name: "SlotsLayout",
|
||||
path: "/crud/slots/layout",
|
||||
component: "/crud/slots/layout/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单占位插槽",
|
||||
name: "SlotsForm",
|
||||
path: "/crud/slots/form",
|
||||
component: "/crud/slots/form/index.vue"
|
||||
},
|
||||
{
|
||||
title: "查询字段插槽",
|
||||
name: "SlotsSearch",
|
||||
path: "/crud/slots/search",
|
||||
component: "/crud/slots/search/index.vue"
|
||||
},
|
||||
{
|
||||
title: "单元格插槽",
|
||||
name: "SlotsCell",
|
||||
path: "/crud/slots/cell",
|
||||
component: "/crud/slots/cell/index.vue"
|
||||
},
|
||||
{
|
||||
title: "表单字段插槽",
|
||||
name: "SlotsFormItem",
|
||||
path: "/crud/slots/form-item",
|
||||
component: "/crud/slots/form-item/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "复杂需求",
|
||||
name: "Advanced",
|
||||
path: "/crud/advanced",
|
||||
redirect: "/crud/advanced/linkage",
|
||||
meta: {
|
||||
icon: "ion:flame-outline"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "选择联动",
|
||||
name: "AdvancedLinkage",
|
||||
path: "/crud/advanced/linkage",
|
||||
component: "/crud/advanced/linkage/index.vue"
|
||||
},
|
||||
{
|
||||
title: "后台加载crud",
|
||||
name: "AdvancedFormBackend",
|
||||
path: "/crud/advanced/from-backend",
|
||||
component: "/crud/advanced/from-backend/index.vue"
|
||||
},
|
||||
{
|
||||
title: "本地分页",
|
||||
name: "AdvancedLocalPagination",
|
||||
path: "/crud/advanced/local-pagination",
|
||||
component: "/crud/advanced/local-pagination/index.vue"
|
||||
},
|
||||
{
|
||||
title: "嵌套子表格",
|
||||
name: "AdvancedNest",
|
||||
path: "/crud/advanced/nest",
|
||||
component: "/crud/advanced/nest/index.vue"
|
||||
},
|
||||
{
|
||||
title: "对话框中显示crud",
|
||||
name: "AdvancedInDialog",
|
||||
path: "/crud/advanced/in-dialog",
|
||||
component: "/crud/advanced/in-dialog/index.vue"
|
||||
},
|
||||
{
|
||||
title: "大量数据",
|
||||
name: "AdvancedBigData",
|
||||
path: "/crud/advanced/big-data",
|
||||
component: "/crud/advanced/big-data/index.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
|
@ -0,0 +1,61 @@
|
|||
import LayoutPass from "/@/layout/layout-pass.vue";
|
||||
|
||||
export const sysResources = [
|
||||
{
|
||||
title: "系统管理",
|
||||
name: "sys",
|
||||
path: "/sys",
|
||||
redirect: "/sys/authority",
|
||||
component: LayoutPass,
|
||||
meta: {
|
||||
icon: "ion:settings-outline",
|
||||
permission: "sys"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "权限管理",
|
||||
name: "authority",
|
||||
path: "/sys/authority",
|
||||
redirect: "/sys/authority/permission",
|
||||
meta: {
|
||||
icon: "ion:ribbon-outline",
|
||||
//需要校验权限
|
||||
permission: "sys:auth"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "权限资源管理",
|
||||
name: "permission",
|
||||
meta: {
|
||||
icon: "ion:list-outline",
|
||||
//需要校验权限
|
||||
permission: "sys:auth:per:view"
|
||||
},
|
||||
path: "/sys/authority/permission",
|
||||
component: "/sys/authority/permission/index.vue"
|
||||
},
|
||||
{
|
||||
title: "角色管理",
|
||||
name: "role",
|
||||
meta: {
|
||||
icon: "ion:people-outline",
|
||||
permission: "sys:auth:role:view"
|
||||
},
|
||||
path: "/sys/authority/role",
|
||||
component: "/sys/authority/role/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "用户管理",
|
||||
name: "user",
|
||||
meta: {
|
||||
icon: "ion:person-outline",
|
||||
permission: "sys:auth:user:view"
|
||||
},
|
||||
path: "/sys/authority/user",
|
||||
component: "/sys/authority/user/index.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
|
@ -0,0 +1,22 @@
|
|||
import LayoutOutside from "/src/layout/layout-outside.vue";
|
||||
import Error404 from "/src/views/framework/error/404.vue";
|
||||
const errorPage = [{ path: "/:pathMatch(.*)*", name: "not-found", component: Error404 }];
|
||||
export const outsideResource = [
|
||||
{
|
||||
title: "outside",
|
||||
name: "outside",
|
||||
path: "/outside",
|
||||
component: LayoutOutside,
|
||||
children: [
|
||||
{
|
||||
meta: {
|
||||
title: "登录"
|
||||
},
|
||||
name: "login",
|
||||
path: "/login",
|
||||
component: "/framework/login/index.vue"
|
||||
}
|
||||
]
|
||||
},
|
||||
...errorPage
|
||||
];
|
|
@ -0,0 +1,5 @@
|
|||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { createPinia } from "pinia";
|
||||
const store = createPinia();
|
||||
export default {
|
||||
install(app) {
|
||||
app.use(store);
|
||||
}
|
||||
};
|
||||
|
||||
export { store };
|
|
@ -0,0 +1,436 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { cloneDeep, get, uniq } from "lodash-es";
|
||||
import router from "/src/router";
|
||||
import { frameworkRoutes } from "/src/router/resolve";
|
||||
// @ts-ignore
|
||||
import { LocalStorage } from "/src/utils/util.storage";
|
||||
import { useUserStore } from "/src/store/modules/user";
|
||||
const OPENED_CACHE_KEY = "TABS_OPENED";
|
||||
|
||||
interface PageState {
|
||||
// 可以在多页 tab 模式下显示的页面
|
||||
pool: Array<any>;
|
||||
// 当前显示的多页面列表
|
||||
opened: Array<any>;
|
||||
// 已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201
|
||||
openedLoaded: boolean;
|
||||
// 当前页面
|
||||
current: "";
|
||||
// 需要缓存的页面 name
|
||||
keepAlive: Array<any>;
|
||||
inited: boolean;
|
||||
}
|
||||
// 判定是否需要缓存
|
||||
const isKeepAlive = (data) => get(data, "meta.cache", false);
|
||||
|
||||
export const usePageStore = defineStore({
|
||||
id: "app.page",
|
||||
state: (): PageState => ({
|
||||
// 可以在多页 tab 模式下显示的页面
|
||||
pool: [],
|
||||
// 当前显示的多页面列表
|
||||
opened: [
|
||||
{
|
||||
name: "index",
|
||||
fullPath: "/index",
|
||||
meta: {
|
||||
title: "首页",
|
||||
auth: false
|
||||
}
|
||||
}
|
||||
],
|
||||
// 已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201
|
||||
openedLoaded: false,
|
||||
// 当前页面
|
||||
current: "",
|
||||
// 需要缓存的页面 name
|
||||
keepAlive: [],
|
||||
inited: false
|
||||
}),
|
||||
getters: {
|
||||
getOpened() {
|
||||
// @ts-ignore
|
||||
return this.opened;
|
||||
},
|
||||
getCurrent(): string {
|
||||
return this.current;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* @description 确认已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201
|
||||
* @param {Object} context
|
||||
*/
|
||||
async isLoaded() {
|
||||
if (this.openedLoaded) {
|
||||
return true;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const timer = setInterval(() => {
|
||||
if (this.openedLoaded) {
|
||||
resolve(clearInterval(timer));
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 从持久化数据载入标签页列表
|
||||
* @param {Object} context
|
||||
*/
|
||||
async openedLoad() {
|
||||
// store 赋值
|
||||
const value = LocalStorage.get(this.getStorageKey());
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
// 在处理函数中进行数据优化 过滤掉现在已经失效的页签或者已经改变了信息的页签
|
||||
// 以 fullPath 字段为准
|
||||
// 如果页面过多的话可能需要优化算法
|
||||
// valid 有效列表 1, 1, 0, 1 => 有效, 有效, 失效, 有效
|
||||
const valid: Array<number> = [];
|
||||
// 处理数据
|
||||
this.opened = value
|
||||
.map((opened) => {
|
||||
// 忽略首页
|
||||
if (opened.fullPath === "/index") {
|
||||
valid.push(1);
|
||||
return opened;
|
||||
}
|
||||
// 尝试在所有的支持多标签页的页面里找到 name 匹配的页面
|
||||
const find = this.pool.find((item) => item.name === opened.name);
|
||||
// 记录有效或无效信息
|
||||
valid.push(find ? 1 : 0);
|
||||
// 返回合并后的数据 新的覆盖旧的
|
||||
// 新的数据中一般不会携带 params 和 query, 所以旧的参数会留存
|
||||
return Object.assign({}, opened, find);
|
||||
})
|
||||
.filter((opened, index) => valid[index] === 1);
|
||||
// 标记已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201
|
||||
this.openedLoaded = true;
|
||||
// 根据 opened 数据生成缓存设置
|
||||
this.keepAliveRefresh();
|
||||
},
|
||||
|
||||
getStorageKey() {
|
||||
const userStore = useUserStore();
|
||||
const userId = userStore.getUserInfo?.id ?? "anonymous";
|
||||
return OPENED_CACHE_KEY + ":" + userId;
|
||||
},
|
||||
/**
|
||||
* 将 opened 属性赋值并持久化 在这之前请先确保已经更新了 state.opened
|
||||
* @param {Object} context
|
||||
*/
|
||||
async opened2db() {
|
||||
// 设置数据
|
||||
|
||||
LocalStorage.set(this.getStorageKey(), this.opened);
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 更新页面列表上的某一项
|
||||
* @param {Object} context
|
||||
* @param {Object} payload { index, params, query, fullPath } 路由信息
|
||||
*/
|
||||
async openedUpdate({ index, params, query, fullPath }) {
|
||||
// 更新页面列表某一项
|
||||
const page = this.opened[index];
|
||||
page.params = params || page.params;
|
||||
page.query = query || page.query;
|
||||
page.fullPath = fullPath || page.fullPath;
|
||||
this.opened.splice(index, 1, page);
|
||||
// 持久化
|
||||
await this.opened2db();
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 重排页面列表上的某一项
|
||||
* @param {Object} context
|
||||
* @param {Object} payload { oldIndex, newIndex } 位置信息
|
||||
*/
|
||||
async openedSort({ oldIndex, newIndex }) {
|
||||
// 重排页面列表某一项
|
||||
const page = this.opened[oldIndex];
|
||||
this.opened.splice(oldIndex, 1);
|
||||
this.opened.splice(newIndex, 0, page);
|
||||
// 持久化
|
||||
await this.opened2db();
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 新增一个 tag (打开一个页面)
|
||||
* @param {Object} context
|
||||
* @param {Object} payload new tag info
|
||||
*/
|
||||
async add({ tag, params, query, fullPath }) {
|
||||
// 设置新的 tag 在新打开一个以前没打开过的页面时使用
|
||||
const newTag = tag;
|
||||
newTag.params = params || newTag.params;
|
||||
newTag.query = query || newTag.query;
|
||||
newTag.fullPath = fullPath || newTag.fullPath;
|
||||
// 添加进当前显示的页面数组
|
||||
this.opened.push(newTag);
|
||||
// 如果这个页面需要缓存 将其添加到缓存设置
|
||||
if (isKeepAlive(newTag)) {
|
||||
this.keepAlivePush(tag.name);
|
||||
}
|
||||
// 持久化
|
||||
await this.opened2db();
|
||||
},
|
||||
/**
|
||||
* @class current
|
||||
* @description 打开一个新的页面
|
||||
* @param {Object} context
|
||||
* @param {Object} payload 从路由钩子的 to 对象上获取 { name, params, query, fullPath, meta } 路由信息
|
||||
*/
|
||||
async open({ name, params, query, fullPath, meta }) {
|
||||
// 已经打开的页面
|
||||
const opened = this.opened;
|
||||
// 判断此页面是否已经打开 并且记录位置
|
||||
let pageOpendIndex = 0;
|
||||
const pageOpend = opened.find((page, index) => {
|
||||
const same = page.fullPath === fullPath;
|
||||
pageOpendIndex = same ? index : pageOpendIndex;
|
||||
return same;
|
||||
});
|
||||
if (pageOpend) {
|
||||
// 页面以前打开过
|
||||
await this.openedUpdate({
|
||||
index: pageOpendIndex,
|
||||
params,
|
||||
query,
|
||||
fullPath
|
||||
});
|
||||
} else {
|
||||
// 页面以前没有打开过
|
||||
const page = this.pool.find((t) => t.name === name);
|
||||
// 如果这里没有找到 page 代表这个路由虽然在框架内 但是不参与标签页显示
|
||||
if (page) {
|
||||
this.add({
|
||||
tag: Object.assign({}, page),
|
||||
params,
|
||||
query,
|
||||
fullPath
|
||||
});
|
||||
}
|
||||
}
|
||||
// 如果这个页面需要缓存 将其添加到缓存设置
|
||||
if (isKeepAlive({ meta })) {
|
||||
this.keepAlivePush(name);
|
||||
}
|
||||
// 设置当前的页面
|
||||
this.currentSet(fullPath);
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 关闭一个 tag (关闭一个页面)
|
||||
* @param {Object} context
|
||||
* @param {Object} payload { tagName: 要关闭的标签名字 }
|
||||
*/
|
||||
async close({ tagName }) {
|
||||
// 预定下个新页面
|
||||
let newPage = {};
|
||||
const isCurrent = this.current === tagName;
|
||||
// 如果关闭的页面就是当前显示的页面
|
||||
if (isCurrent) {
|
||||
// 去找一个新的页面
|
||||
const len = this.opened.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (this.opened[i].fullPath === tagName) {
|
||||
newPage = i < len - 1 ? this.opened[i + 1] : this.opened[i - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 找到这个页面在已经打开的数据里是第几个
|
||||
const index = this.opened.findIndex((page) => page.fullPath === tagName);
|
||||
if (index >= 0) {
|
||||
// 如果这个页面是缓存的页面 将其在缓存设置中删除
|
||||
this.keepAliveRemove(this.opened[index].name);
|
||||
// 更新数据 删除关闭的页面
|
||||
this.opened.splice(index, 1);
|
||||
}
|
||||
// 持久化
|
||||
await this.opened2db();
|
||||
// 决定最后停留的页面
|
||||
if (isCurrent) {
|
||||
// @ts-ignore
|
||||
const { name = "index", params = {}, query = {} } = newPage;
|
||||
const routerObj = { name, params, query };
|
||||
await router.push(routerObj);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 关闭当前标签左边的标签
|
||||
* @param opts
|
||||
*/
|
||||
async closeLeft(opts = {}) {
|
||||
await this.closeByCondition({
|
||||
condition({ i, currentIndex }) {
|
||||
return i >= currentIndex;
|
||||
},
|
||||
...opts
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 关闭当前标签右边的标签
|
||||
* @param opts
|
||||
*/
|
||||
async closeRight(opts = {}) {
|
||||
await this.closeByCondition({
|
||||
condition({ i, currentIndex }) {
|
||||
return currentIndex >= i;
|
||||
},
|
||||
...opts
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 关闭当前标签右边的标签
|
||||
* @param opts
|
||||
*/
|
||||
async closeByCondition(opts = {}) {
|
||||
// @ts-ignore
|
||||
const { pageSelect, condition } = opts;
|
||||
const pageAim = pageSelect || this.current;
|
||||
let currentIndex = 0;
|
||||
this.opened.forEach((page, index) => {
|
||||
if (page.fullPath === pageAim) currentIndex = index;
|
||||
});
|
||||
// 删除打开的页面 并在缓存设置中删除
|
||||
for (let i = this.opened.length - 1; i >= 0; i--) {
|
||||
if (this.opened[i].name === "index" || condition({ i, currentIndex })) {
|
||||
continue;
|
||||
}
|
||||
this.keepAliveRemove(this.opened[i].name);
|
||||
this.opened.splice(i, 1);
|
||||
}
|
||||
// 持久化
|
||||
await this.opened2db();
|
||||
// 设置当前的页面
|
||||
this.current = pageAim;
|
||||
// @ts-ignore
|
||||
if (router.currentRoute.fullPath !== pageAim) await router.push(pageAim);
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 关闭当前激活之外的 tag
|
||||
* @param opts
|
||||
*/
|
||||
async closeOther(opts = {}) {
|
||||
await this.closeByCondition({
|
||||
condition({ i, currentIndex }) {
|
||||
return currentIndex === i;
|
||||
},
|
||||
...opts
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @class opened
|
||||
* @description 关闭所有 tag
|
||||
* @param {Object} context
|
||||
*/
|
||||
async closeAll() {
|
||||
// 删除打开的页面 并在缓存设置中删除
|
||||
for (let i = this.opened.length - 1; i >= 0; i--) {
|
||||
if (this.opened[i].name === "index") {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.keepAliveRemove(this.opened[i].name);
|
||||
this.opened.splice(i, 1);
|
||||
}
|
||||
// 持久化
|
||||
await this.opened2db();
|
||||
// 关闭所有的标签页后需要判断一次现在是不是在首页
|
||||
// @ts-ignore
|
||||
if (router.currentRoute.name !== "index") {
|
||||
await router.push({ name: "index" });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @class keepAlive
|
||||
* @description 从已经打开的页面记录中更新需要缓存的页面记录
|
||||
* @param {Object} state state
|
||||
*/
|
||||
keepAliveRefresh() {
|
||||
this.keepAlive = this.opened.filter((item) => isKeepAlive(item)).map((e) => e.name);
|
||||
console.log("keep alive:", this.keepAlive);
|
||||
},
|
||||
/**
|
||||
* @description 删除一个页面的缓存设置
|
||||
* @param {Object} state state
|
||||
* @param {String} name name
|
||||
*/
|
||||
keepAliveRemove(name) {
|
||||
const list = cloneDeep(this.keepAlive);
|
||||
const index = list.findIndex((item) => item === name);
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1);
|
||||
this.keepAlive = list;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 增加一个页面的缓存设置
|
||||
* @param {Object} state state
|
||||
* @param {String} name name
|
||||
*/
|
||||
keepAlivePush(name) {
|
||||
const keep = cloneDeep(this.keepAlive);
|
||||
keep.push(name);
|
||||
this.keepAlive = uniq(keep);
|
||||
},
|
||||
/**
|
||||
* @description 清空页面缓存设置
|
||||
* @param {Object} state state
|
||||
*/
|
||||
keepAliveClean() {
|
||||
this.keepAlive = [];
|
||||
},
|
||||
/**
|
||||
* @class current
|
||||
* @description 设置当前激活的页面 fullPath
|
||||
* @param {Object} state state
|
||||
* @param {String} fullPath new fullPath
|
||||
*/
|
||||
currentSet(fullPath) {
|
||||
this.current = fullPath;
|
||||
},
|
||||
/**
|
||||
* @class pool
|
||||
* @description 保存 pool (候选池)
|
||||
* @param {Object} state state
|
||||
* @param {Array} routes routes
|
||||
*/
|
||||
async init(routes) {
|
||||
if (this.inited) {
|
||||
return;
|
||||
}
|
||||
this.inited = true;
|
||||
if (routes == null) {
|
||||
//不能用全部的routes,只能是framework内的
|
||||
routes = frameworkRoutes;
|
||||
}
|
||||
|
||||
const pool = [];
|
||||
const push = function (routes) {
|
||||
routes.forEach((route) => {
|
||||
if (route.children && route.children.length > 0) {
|
||||
push(route.children);
|
||||
} else {
|
||||
if (!route.hidden) {
|
||||
const { meta, name, path } = route;
|
||||
// @ts-ignore
|
||||
pool.push({ meta, name, path });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
push(routes);
|
||||
this.pool = pool;
|
||||
await this.openedLoad();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,127 @@
|
|||
import { defineStore } from "pinia";
|
||||
// @ts-ignore
|
||||
import { frameworkMenus, headerMenus, filterMenus, findMenus } from "/src/router/resolve";
|
||||
import _ from "lodash-es";
|
||||
import { mitter } from "/src/utils/util.mitt";
|
||||
//监听注销事件
|
||||
mitter.on("app.logout", () => {
|
||||
const resourceStore = useResourceStore();
|
||||
resourceStore.clear();
|
||||
});
|
||||
|
||||
interface ResourceState {
|
||||
frameworkMenus: Array<any>;
|
||||
headerMenus: Array<any>;
|
||||
asideMenus: Array<any>;
|
||||
fixedAsideMenus: Array<any>;
|
||||
inited: boolean;
|
||||
currentAsidePath: string;
|
||||
}
|
||||
|
||||
export const useResourceStore = defineStore({
|
||||
id: "app.resource",
|
||||
state: (): ResourceState => ({
|
||||
// user info
|
||||
frameworkMenus: [],
|
||||
headerMenus: [],
|
||||
asideMenus: [],
|
||||
fixedAsideMenus: [],
|
||||
inited: false,
|
||||
currentAsidePath: ""
|
||||
}),
|
||||
getters: {
|
||||
getAsideMenus() {
|
||||
return this.asideMenus;
|
||||
},
|
||||
getHeaderMenus() {
|
||||
return this.headerMenus;
|
||||
},
|
||||
getFrameworkMenus() {
|
||||
return this.frameworkMenus;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
clear() {
|
||||
this.inited = false;
|
||||
},
|
||||
/**
|
||||
* 初始化资源
|
||||
*/
|
||||
init() {
|
||||
if (this.inited) {
|
||||
return;
|
||||
}
|
||||
this.inited = true;
|
||||
|
||||
const showMenus = _.cloneDeep(frameworkMenus[0].children);
|
||||
this.frameworkMenus = filterMenus(showMenus, (item) => {
|
||||
return item?.meta?.showOnHeader !== false;
|
||||
});
|
||||
|
||||
this.fixedAsideMenus = findMenus(showMenus, (item) => {
|
||||
return item?.meta?.fixedAside === true;
|
||||
});
|
||||
this.headerMenus = headerMenus;
|
||||
this.setAsideMenu();
|
||||
},
|
||||
setAsideMenu(topMenu?) {
|
||||
if (this.frameworkMenus.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (topMenu == null) {
|
||||
topMenu = this.frameworkMenus[0];
|
||||
}
|
||||
const asideMenus = topMenu?.children || [];
|
||||
this.asideMenus = [...this.fixedAsideMenus, ...asideMenus];
|
||||
},
|
||||
setAsideMenuByCurrentRoute(matched) {
|
||||
const menuHeader = this.frameworkMenus;
|
||||
if (matched?.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
function findFromTree(tree, find) {
|
||||
const results: Array<any> = [];
|
||||
for (const item of tree) {
|
||||
if (find(item)) {
|
||||
results.push(item);
|
||||
return results;
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
const found = findFromTree(item.children, find);
|
||||
if (found) {
|
||||
results.push(item);
|
||||
return results.concat(found);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const matchedPath = matched[1].path;
|
||||
const _side = findFromTree(menuHeader, (menu) => menu.path === matchedPath);
|
||||
if (_side?.length > 0) {
|
||||
if (this.currentAsidePath === _side[0]) {
|
||||
return;
|
||||
}
|
||||
this.currentAsidePath = _side[0];
|
||||
this.setAsideMenu(_side[0]);
|
||||
}
|
||||
},
|
||||
filterByPermission(permissions) {
|
||||
this.frameworkMenus = this.filterChildrenByPermission(this.frameworkMenus, permissions);
|
||||
},
|
||||
filterChildrenByPermission(list, permissions) {
|
||||
const menus = list.filter((item) => {
|
||||
if (item?.meta?.permission) {
|
||||
return permissions.includes(item.meta.permission);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
for (const menu of menus) {
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
menu.children = this.filterChildrenByPermission(menu.children, permissions);
|
||||
}
|
||||
}
|
||||
return menus;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,63 @@
|
|||
import { defineStore } from "pinia";
|
||||
// @ts-ignore
|
||||
import { LocalStorage } from "/src/utils/util.storage";
|
||||
// import { replaceStyleVariables } from "vite-plugin-theme/es/client";
|
||||
|
||||
// import { getThemeColors, generateColors } from "/src/../build/theme-colors";
|
||||
//
|
||||
// import { mixLighten, mixDarken, tinycolor } from "vite-plugin-theme/es/colorUtils";
|
||||
|
||||
// export async function changeTheme(color?: string) {
|
||||
// if (color == null) {
|
||||
// return;
|
||||
// }
|
||||
// const colors = generateColors({
|
||||
// mixDarken,
|
||||
// mixLighten,
|
||||
// tinycolor,
|
||||
// color
|
||||
// });
|
||||
//
|
||||
// return await replaceStyleVariables({
|
||||
// colorVariables: [...getThemeColors(color), ...colors]
|
||||
// });
|
||||
// }
|
||||
|
||||
interface SettingState {
|
||||
theme: any;
|
||||
}
|
||||
|
||||
const SETTING_THEME_KEY = "SETTING_THEME";
|
||||
export const useSettingStore = defineStore({
|
||||
id: "app.setting",
|
||||
state: (): SettingState => ({
|
||||
// user info
|
||||
theme: null
|
||||
}),
|
||||
getters: {
|
||||
getTheme(): any {
|
||||
return this.theme || LocalStorage.get(SETTING_THEME_KEY) || {};
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
persistTheme() {
|
||||
LocalStorage.set(SETTING_THEME_KEY, this.getTheme);
|
||||
},
|
||||
async setTheme(theme?: Object) {
|
||||
if (theme == null) {
|
||||
theme = this.getTheme;
|
||||
}
|
||||
this.theme = theme;
|
||||
this.persistTheme();
|
||||
// await changeTheme(this.theme.primaryColor);
|
||||
},
|
||||
async setPrimaryColor(color) {
|
||||
const theme = this.theme;
|
||||
theme.primaryColor = color;
|
||||
await this.setTheme();
|
||||
},
|
||||
async init() {
|
||||
await this.setTheme(this.getTheme);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,106 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { store } from "../index";
|
||||
import router from "../../router";
|
||||
// @ts-ignore
|
||||
import { LocalStorage } from "/src/utils/util.storage";
|
||||
// @ts-ignore
|
||||
import * as UserApi from "/src/api/modules/api.user";
|
||||
// @ts-ignore
|
||||
import { LoginReq, UserInfoRes } from "/@/api/modules/api.user";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import { mitter } from "/src/utils/util.mitt";
|
||||
|
||||
interface UserState {
|
||||
userInfo: Nullable<UserInfoRes>;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
const USER_INFO_KEY = "USER_INFO";
|
||||
const TOKEN_KEY = "TOKEN";
|
||||
export const useUserStore = defineStore({
|
||||
id: "app.user",
|
||||
state: (): UserState => ({
|
||||
// user info
|
||||
userInfo: null,
|
||||
// token
|
||||
token: undefined
|
||||
}),
|
||||
getters: {
|
||||
getUserInfo(): UserInfoRes {
|
||||
return this.userInfo || LocalStorage.get(USER_INFO_KEY) || {};
|
||||
},
|
||||
getToken(): string {
|
||||
return this.token || LocalStorage.get(TOKEN_KEY);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setToken(info: string, expire: number) {
|
||||
this.token = info;
|
||||
LocalStorage.set(TOKEN_KEY, this.token, expire);
|
||||
},
|
||||
setUserInfo(info: UserInfoRes) {
|
||||
this.userInfo = info;
|
||||
LocalStorage.set(USER_INFO_KEY, info);
|
||||
},
|
||||
resetState() {
|
||||
this.userInfo = null;
|
||||
this.token = "";
|
||||
LocalStorage.remove(TOKEN_KEY);
|
||||
LocalStorage.remove(USER_INFO_KEY);
|
||||
},
|
||||
/**
|
||||
* @description: login
|
||||
*/
|
||||
async login(params: LoginReq): Promise<any> {
|
||||
try {
|
||||
const data = await UserApi.login(params);
|
||||
const { token, expire } = data;
|
||||
|
||||
// save token
|
||||
this.setToken(token, expire);
|
||||
// get user info
|
||||
const userInfo = await this.getUserInfoAction();
|
||||
await router.replace("/");
|
||||
mitter.emit("app.login", { userInfo, token: data });
|
||||
return userInfo;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async getUserInfoAction(): Promise<UserInfoRes> {
|
||||
const userInfo = await UserApi.mine();
|
||||
this.setUserInfo(userInfo);
|
||||
return userInfo;
|
||||
},
|
||||
/**
|
||||
* @description: logout
|
||||
*/
|
||||
logout(goLogin = true) {
|
||||
this.resetState();
|
||||
goLogin && router.push("/login");
|
||||
mitter.emit("app.logout");
|
||||
},
|
||||
|
||||
/**
|
||||
* @description: Confirm before logging out
|
||||
*/
|
||||
confirmLoginOut() {
|
||||
const { t } = useI18n();
|
||||
Modal.config({
|
||||
iconType: "warning",
|
||||
title: t("app.login.logoutTip"),
|
||||
content: t("app.login.logoutMessage"),
|
||||
onOk: async () => {
|
||||
await this.logout(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useUserStoreWidthOut() {
|
||||
return useUserStore(store);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/*占位,勿删*/
|
|
@ -0,0 +1,124 @@
|
|||
@import './theme/index.less';
|
||||
@import './theme/default.less';
|
||||
@import './scroll.less';
|
||||
@import './transition.less';
|
||||
@import './fix-windicss.less';
|
||||
svg { vertical-align: baseline; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div#app {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
padding: 4px 11px;
|
||||
}
|
||||
|
||||
|
||||
.anticon {
|
||||
vertical-align: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.red{
|
||||
color:red
|
||||
}
|
||||
|
||||
.font12{
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bg-gray{
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.bg-white{
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.fs-page-header{
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.ant-btn{
|
||||
.fs-iconify{
|
||||
font-size:16px;
|
||||
margin-right:3px
|
||||
}
|
||||
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.ant-timeline{
|
||||
.fs-iconify{
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.mt-10{
|
||||
margin-top:10px;
|
||||
}
|
||||
.ml-5{
|
||||
margin-left:5px;
|
||||
}
|
||||
.ml-10{
|
||||
margin-left: 10px;
|
||||
}
|
||||
.mtb-5{
|
||||
margin: 5px 0 5px 0;
|
||||
}
|
||||
|
||||
.mb-10{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mlr-5{
|
||||
margin: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.gray{
|
||||
color:gray;
|
||||
}
|
||||
.green{
|
||||
color:green;
|
||||
}
|
||||
.blue{
|
||||
color:blue;
|
||||
}
|
||||
.red{
|
||||
color:red
|
||||
}
|
||||
.yellow{
|
||||
color:yellow;
|
||||
}
|
||||
|
||||
.ml-2{
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.font-20{
|
||||
font-size:20px
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
img.ant-image-preview-img{
|
||||
display: initial;
|
||||
}
|