• Kotlin高仿微信-第27篇-朋友圈-相册选择图片或小视频


     Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。

    Kotlin高仿微信-项目实践58篇,点击查看详情

    效果图:

    实现代码:

    fun openAblum(){
        // 打开相册
        ImageSelector.builder()
            .useCamera(false) // 设置是否使用拍照
            .setSingle(false) //设置是否单选
            .canPreview(true) //是否点击放大图片查看,,默认为true
            .start(this,REQ_PICTURE_CODE)
    
    }

    
    
    
        
    
            
    
                
    
            
    
            
    
                
    
            
    
            
    
            
    
            
    
        
    
        
    
        
    
        
    
        
    
        
    
            
    
                
    
                    
    
                    
    
                
    
            
    
            
    
                
    
            
    
            
    
        
    
    

    /**
     * author : wangning
     * email : maoning20080809@163.com
     * Date : 2022/4/15 16:44
     * description : 选择相册
     */
    class ImageSelectorActivity : AppCompatActivity() {
    
        private var mAdapter: ImageAdapter? = null
        private var mLayoutManager: GridLayoutManager? = null
    
        private var mFolders: ArrayList? = null
        private var mFolder: Folder? = null
        private var applyLoadImage = false
        private var applyCamera = false
        private val PERMISSION_WRITE_EXTERNAL_REQUEST_CODE = 0x00000011
        private val PERMISSION_CAMERA_REQUEST_CODE = 0x00000012
    
        private val CAMERA_REQUEST_CODE = 0x00000010
        private var mCameraUri: Uri? = null
        private var mCameraImagePath: String? = null
        private var mTakeTime: Long = 0
    
        private var isOpenFolder = false
        private var isShowTime = false
        private var isInitFolder = false
        private var isSingle = false
        private var canPreview = true
        private var mMaxCount = 0
    
        private var useCamera = true
        private var onlyTakePhoto = false
    
        private val mHideHandler = Handler()
        private val mHide = Runnable { hideTime() }
    
        //用于接收从外面传进来的已选择的图片列表。当用户原来已经有选择过图片,现在重新打开选择器,允许用
        // 户把先前选过的图片传进来,并把这些图片默认为选中状态。
        private var mSelectedImages: ArrayList? = null
    
        private val REQ_VIDEO_CODE = 102
    
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val intent = intent
            val config: RequestConfig = intent.getParcelableExtra(ImageSelector.KEY_CONFIG)!!
            mMaxCount = config.maxSelectCount
            isSingle = config.isSingle
            canPreview = config.canPreview
            useCamera = config.useCamera
            mSelectedImages = config.selected
            onlyTakePhoto = config.onlyTakePhoto
            if (onlyTakePhoto) {
                // 仅拍照
                checkPermissionAndCamera()
            } else {
                setContentView(R.layout.wc_album_image_select)
                setStatusBarColor()
                initListener()
                initImageList()
                checkPermissionAndLoadImages()
                hideFolderList()
                setSelectImageCount(0)
            }
        }
    
        /**
         * 修改状态栏颜色
         */
        private fun setStatusBarColor() {
            if (VersionUtils.isAndroidL()) {
                window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
                window.statusBarColor = Color.parseColor("#373c3d")
            }
        }
    
        private fun initListener() {
            btn_back.setOnClickListener { finish() }
            btn_preview.setOnClickListener {
                val images = ArrayList()
                mAdapter?.getSelectImages()?.let { it -> images.addAll(it) }
                toPreviewActivity(images, 0)
            }
            //选择小视频
            select_video.setOnClickListener {
                val i = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                startActivityForResult(i, REQ_VIDEO_CODE)
            }
            btn_confirm.setOnClickListener { confirm() }
            btn_folder.setOnClickListener {
                if (isInitFolder) {
                    if (isOpenFolder) {
                        closeFolder()
                    } else {
                        openFolder()
                    }
                }
            }
            masking.setOnClickListener { closeFolder() }
            rv_image.addOnScrollListener(object : RecyclerView.OnScrollListener() {
                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                    super.onScrollStateChanged(recyclerView, newState)
                    changeTime()
                }
    
                override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                    super.onScrolled(recyclerView, dx, dy)
                    changeTime()
                }
            })
        }
    
        /**
         * 初始化图片列表
         */
        private fun initImageList() {
            // 判断屏幕方向
            val configuration = resources.configuration
            mLayoutManager = if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
                GridLayoutManager(this, 3)
            } else {
                GridLayoutManager(this, 5)
            }
            rv_image.layoutManager = mLayoutManager
            mAdapter = ImageAdapter(this, mMaxCount, isSingle, canPreview)
            rv_image.adapter = mAdapter
            (rv_image.itemAnimator as SimpleItemAnimator?)!!.supportsChangeAnimations = false
            if (mFolders != null && !mFolders!!.isEmpty()) {
                setFolder(mFolders!![0])
            }
            mAdapter?.setOnImageSelectListener(object : ImageAdapter.OnImageSelectListener {
                override fun onImageSelect(image: Image?, isSelect: Boolean, selectCount: Int) {
                    setSelectImageCount(selectCount)
                }
            })
            mAdapter?.setOnItemClickListener(object : ImageAdapter.OnItemClickListener {
    
                override fun onItemClick(image: Image?, position: Int) {
                    toPreviewActivity(mAdapter?.getData(), position)
                }
    
                override fun onCameraClick() {
                    checkPermissionAndCamera()
                }
            })
        }
    
        /**
         * 初始化图片文件夹列表
         */
        private fun initFolderList() {
            if (mFolders != null && !mFolders!!.isEmpty()) {
                isInitFolder = true
                rv_folder.layoutManager = LinearLayoutManager(this@ImageSelectorActivity)
                val adapter = FolderAdapter(this@ImageSelectorActivity, mFolders)
                adapter.setOnFolderSelectListener(object : FolderAdapter.OnFolderSelectListener {
                    override fun onFolderSelect(folder: Folder?) {
                        setFolder(folder)
                        closeFolder()
                    }
                })
                rv_folder.adapter = adapter
            }
        }
    
        /**
         * 刚开始的时候文件夹列表默认是隐藏的
         */
        private fun hideFolderList() {
            rv_folder.post {
                rv_folder.translationY = rv_folder.height.toFloat()
                rv_folder.visibility = View.GONE
                rv_folder.setBackgroundColor(Color.WHITE)
            }
        }
    
        /**
         * 设置选中的文件夹,同时刷新图片列表
         * @param folder
         */
        private fun setFolder(folder: Folder?) {
            if (folder != null && mAdapter != null && !folder.equals(mFolder)) {
                mFolder = folder
                tv_folder_name.text = folder.getName()
                rv_image.scrollToPosition(0)
                mAdapter?.refresh(folder.getImages(), folder.isUseCamera())
            }
        }
    
        private fun setSelectImageCount(count: Int) {
            if (count == 0) {
                btn_confirm.isEnabled = false
                btn_preview.isEnabled = false
                tv_confirm.setText(R.string.selector_send)
                tv_preview.setText(R.string.selector_preview)
            } else {
                btn_confirm.isEnabled = true
                btn_preview.isEnabled = true
                tv_preview.text = getString(R.string.selector_preview) + "(" + count + ")"
                if (isSingle) {
                    tv_confirm.setText(R.string.selector_send)
                } else if (mMaxCount > 0) {
                    tv_confirm.text = getString(R.string.selector_send) + "(" + count + "/" + mMaxCount + ")"
                } else {
                    tv_confirm.text = getString(R.string.selector_send) + "(" + count + ")"
                }
            }
        }
    
        /**
         * 弹出文件夹列表
         */
        @SuppressLint("ObjectAnimatorBinding")
        private fun openFolder() {
            if (!isOpenFolder) {
                masking.visibility = View.VISIBLE
                val animator = ObjectAnimator.ofFloat(rv_folder, "translationY",rv_folder.height.toFloat(), 0f).setDuration(300)
                animator.addListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationStart(animation: Animator) {
                        super.onAnimationStart(animation)
                        rv_folder.visibility = View.VISIBLE
                    }
                })
                animator.start()
                isOpenFolder = true
            }
        }
    
        /**
         * 收起文件夹列表
         */
        @SuppressLint("ObjectAnimatorBinding")
        private fun closeFolder() {
            if (isOpenFolder) {
                masking.visibility = View.GONE
                val animator = ObjectAnimator.ofFloat(rv_folder, "translationY", 0f, rv_folder.height.toFloat()).setDuration(300)
                    animator.addListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
                        super.onAnimationEnd(animation)
                        rv_folder.visibility = View.GONE
                    }
                })
                animator.start()
                isOpenFolder = false
            }
        }
    
        /**
         * 隐藏时间条
         */
        @SuppressLint("ObjectAnimatorBinding")
        private fun hideTime() {
            if (isShowTime) {
                ObjectAnimator.ofFloat(tv_time, "alpha", 1f, 0f).setDuration(300).start()
                isShowTime = false
            }
        }
    
        /**
         * 显示时间条
         */
        @SuppressLint("ObjectAnimatorBinding")
        private fun showTime() {
            if (!isShowTime) {
                ObjectAnimator.ofFloat(tv_time, "alpha", 0f, 1f).setDuration(300).start()
                isShowTime = true
            }
        }
    
        /**
         * 改变时间条显示的时间(显示图片列表中的第一个可见图片的时间)
         */
        private fun changeTime() {
            val firstVisibleItem = getFirstVisibleItem()
            val image: Image? = mAdapter?.getFirstVisibleImage(firstVisibleItem)
            if (image != null) {
                val time: String = DateUtils.getImageTime(this, image.getTime())
                tv_time.text = time
                showTime()
                mHideHandler.removeCallbacks(mHide)
                mHideHandler.postDelayed(mHide, 1500)
            }
        }
    
        private fun getFirstVisibleItem(): Int {
            return mLayoutManager!!.findFirstVisibleItemPosition()
        }
    
        private fun confirm() {
            if (mAdapter == null) {
                return
            }
            //因为图片的实体类是Image,而我们返回的是String数组,所以要进行转换。
            val selectImages: ArrayList = mAdapter?.getSelectImages()!!
            val images = ArrayList()
            for (image in selectImages) {
                images.add(image.getPath())
            }
            saveImageAndFinish(images, false)
        }
    
        private fun saveImageAndFinish(images: ArrayList, isCameraImage: Boolean) {
            //点击确定,把选中的图片通过Intent传给上一个Activity。
            setResult(images, isCameraImage)
            finish()
        }
    
        private fun setResult(images: ArrayList, isCameraImage: Boolean) {
            val intent = Intent()
            intent.putStringArrayListExtra(ImageSelector.SELECT_RESULT, images)
            intent.putExtra(ImageSelector.IS_CAMERA_IMAGE, isCameraImage)
            setResult(RESULT_OK, intent)
        }
    
        private fun toPreviewActivity(images: ArrayList?, position: Int) {
            if (images != null && !images.isEmpty()) {
                PreviewActivity.openActivity(this, images,mAdapter?.getSelectImages(), isSingle, mMaxCount, position)
            }
        }
    
        override fun onStart() {
            super.onStart()
            if (applyLoadImage) {
                applyLoadImage = false
                checkPermissionAndLoadImages()
            }
            if (applyCamera) {
                applyCamera = false
                checkPermissionAndCamera()
            }
        }
    
        /**
         * 处理图片预览页返回的结果
         *
         * @param requestCode
         * @param resultCode
         * @param data
         */
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            if (requestCode == ImageSelector.RESULT_CODE) {
                if (data != null && data.getBooleanExtra(ImageSelector.IS_CONFIRM, false)) {
                    //如果用户在预览页点击了确定,就直接把用户选中的图片返回给用户。
                    confirm()
                } else {
                    //否则,就刷新当前页面。
                    mAdapter?.notifyDataSetChanged()
                    setSelectImageCount(mAdapter?.getSelectImages()?.size!!)
                }
            } else if (requestCode == CAMERA_REQUEST_CODE) {
                if (resultCode == RESULT_OK) {
                    val images = ArrayList()
                    var savePictureUri: Uri? = null
                    if (VersionUtils.isAndroidQ()) {
                        savePictureUri = mCameraUri
                        images.add(UriUtils.getPathForUri(this, mCameraUri!!))
                    } else {
                        savePictureUri = Uri.fromFile(File(mCameraImagePath))
                        images.add(mCameraImagePath)
                    }
                    ImageUtil.savePicture(this, savePictureUri, mTakeTime)
                    saveImageAndFinish(images, true)
                } else {
                    if (onlyTakePhoto) {
                        finish()
                    }
                }
            } else if (requestCode == REQ_VIDEO_CODE && resultCode == RESULT_OK && null != data) {
                var selectedVideo = data.getData()!!
                var filePathColumn = arrayOf(MediaStore.Video.Media.DATA)
                var cursor : Cursor? = this.contentResolver.query(selectedVideo, filePathColumn, null, null, null)
                cursor?.moveToFirst()
    
                var columnIndex : Int? = cursor?.getColumnIndex(filePathColumn[0])
                var videoPath = cursor?.getString(columnIndex!!)
                cursor?.close()
                TagUtils.d("相册选择小视频路径:${videoPath}")
                /*var bundle = bundleOf(CommonUtils.Moments.TYPE_NAME to CommonUtils.Moments.TYPE_VIDEO, CommonUtils.Moments.TYPE_VIDEO_PATH to videoPath)
                navController?.navigate(R.id.action_moments_publish, bundle)*/
                var sVideoBean = SVideoBean(CommonUtils.Moments.TYPE_VIDEO, videoPath.toString())
                EventBus.getDefault().post(sVideoBean)
                finish()
            }
        }
    
        /**
         * 横竖屏切换处理
         * @param newConfig
         */
        override fun onConfigurationChanged(newConfig: Configuration) {
            super.onConfigurationChanged(newConfig)
            if (mLayoutManager != null && mAdapter != null) {
                //切换为竖屏
                if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
                    mLayoutManager!!.spanCount = 3
                } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    mLayoutManager!!.spanCount = 5
                }
                mAdapter?.notifyDataSetChanged()
            }
        }
    
        /**
         * 检查权限并加载SD卡里的图片。
         */
        private fun checkPermissionAndLoadImages() {
            if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
                Toast.makeText(this, "没有图片", Toast.LENGTH_LONG).show();
                return
            }
            val hasWriteExternalPermission = ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)
            if (hasWriteExternalPermission == PackageManager.PERMISSION_GRANTED) {
                //有权限,加载图片。
                loadImageForSDCard()
            } else {
                //没有权限,申请权限。
                ActivityCompat.requestPermissions(this@ImageSelectorActivity,arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),PERMISSION_WRITE_EXTERNAL_REQUEST_CODE)
            }
        }
    
        /**
         * 检查权限并拍照。
         */
        private fun checkPermissionAndCamera() {
            val hasCameraPermission =
                ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            val hasWriteExternalPermission = ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
            if (hasCameraPermission == PackageManager.PERMISSION_GRANTED
                && hasWriteExternalPermission == PackageManager.PERMISSION_GRANTED
            ) {
                //有调起相机拍照。
                openCamera()
            } else {
                //没有权限,申请权限。
                ActivityCompat.requestPermissions(
                    this@ImageSelectorActivity,
                    arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    PERMISSION_CAMERA_REQUEST_CODE
                )
            }
        }
    
        /**
         * 处理权限申请的回调。
         *
         * @param requestCode
         * @param permissions
         * @param grantResults
         */
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array,
            grantResults: IntArray
        ) {
            if (requestCode == PERMISSION_WRITE_EXTERNAL_REQUEST_CODE) {
                if (grantResults.size > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED
                ) {
                    //允许权限,加载图片。
                    loadImageForSDCard()
                } else {
                    //拒绝权限,弹出提示框。
                    showExceptionDialog(true)
                }
            } else if (requestCode == PERMISSION_CAMERA_REQUEST_CODE) {
                if (grantResults.size > 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                    //允许权限,有调起相机拍照。
                    openCamera()
                } else {
                    //拒绝权限,弹出提示框。
                    showExceptionDialog(false)
                }
            }
        }
    
        /**
         * 发生没有权限等异常时,显示一个提示dialog.
         */
        private fun showExceptionDialog(applyLoad: Boolean) {
            AlertDialog.Builder(this)
                .setCancelable(false)
                .setTitle(R.string.selector_hint)
                .setMessage(R.string.selector_permissions_hint)
                .setNegativeButton(R.string.selector_cancel,
                    DialogInterface.OnClickListener { dialog, which ->
                        dialog.cancel()
                        finish()
                    }).setPositiveButton(R.string.selector_confirm,
                    DialogInterface.OnClickListener { dialog, which ->
                        dialog.cancel()
                        startAppSettings()
                        if (applyLoad) {
                            applyLoadImage = true
                        } else {
                            applyCamera = true
                        }
                    }).show()
        }
    
        /**
         * 从SDCard加载图片。
         */
        private fun loadImageForSDCard() {
            ImageModel.loadImageForSDCard(this, object : ImageModel.Companion.DataCallback{
                override fun onSuccess(folders: ArrayList?) {
                    mFolders = folders
                    runOnUiThread {
                        if (mFolders != null && !mFolders!!.isEmpty()) {
                            initFolderList()
                            mFolders!![0].setUseCamera(useCamera)
                            setFolder(mFolders!![0])
                            if (mSelectedImages != null && mAdapter != null) {
                                mAdapter?.setSelectedImages(mSelectedImages)
                                mSelectedImages = null
                                setSelectImageCount(mAdapter?.getSelectImages()?.size!!)
                            }
                        }
                    }
                }
            })
        }
    
        /**
         * 调起相机拍照
         */
        private fun openCamera() {
            val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            if (captureIntent.resolveActivity(packageManager) != null) {
                var photoFile: File? = null
                var photoUri: Uri? = null
                if (VersionUtils.isAndroidQ()) {
                    photoUri = createImagePathUri()
                } else {
                    try {
                        photoFile = createImageFile()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                    if (photoFile != null) {
                        mCameraImagePath = photoFile.absolutePath
                        photoUri = if (VersionUtils.isAndroidN()) {
                            //通过FileProvider创建一个content类型的Uri
                            FileProvider.getUriForFile(this, "$packageName.fileProvider", photoFile)
                        } else {
                            Uri.fromFile(photoFile)
                        }
                    }
                }
                mCameraUri = photoUri
                if (photoUri != null) {
                    captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
                    captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                    startActivityForResult(captureIntent, CAMERA_REQUEST_CODE)
                    mTakeTime = System.currentTimeMillis()
                }
            }
        }
    
        /**
         * 创建一条图片地址uri,用于保存拍照后的照片
         * @return 图片的uri
         */
        fun createImagePathUri(): Uri? {
            val status = Environment.getExternalStorageState()
            val timeFormatter = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
            val time = System.currentTimeMillis()
            val imageName = timeFormatter.format(Date(time))
            // ContentValues是我们希望这条记录被创建时包含的数据信息
            val values = ContentValues(2)
            values.put(MediaStore.Images.Media.DISPLAY_NAME, imageName)
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
            return if (status == Environment.MEDIA_MOUNTED) {
                contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
            } else {
                contentResolver.insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values)
            }
        }
    
        @Throws(IOException::class)
        private fun createImageFile(): File? {
            val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
            val imageFileName = String.format("JPEG_%s.jpg", timeStamp)
            val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
            if (!storageDir.exists()) {
                storageDir.mkdir()
            }
            val tempFile = File(storageDir, imageFileName)
            return if (Environment.MEDIA_MOUNTED != EnvironmentCompat.getStorageState(tempFile)) {
                null
            } else tempFile
        }
    
        /**
         * 启动应用的设置
         */
        private fun startAppSettings() {
            val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
            intent.data = Uri.parse("package:$packageName")
            startActivity(intent)
        }
    
        override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
            if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_DOWN && isOpenFolder) {
                closeFolder()
                return true
            }
            return super.onKeyDown(keyCode, event)
        }
    
        companion object {
            /**
             * 启动图片选择器
             * @param activity
             * @param requestCode
             * @param config
             */
            fun openActivity(activity: Activity, requestCode: Int, config: RequestConfig?) {
                val intent = Intent(activity, ImageSelectorActivity::class.java)
                intent.putExtra(ImageSelector.KEY_CONFIG, config)
                activity.startActivityForResult(intent, requestCode)
            }
    
            /**
             * 启动图片选择器
             * @param fragment
             * @param requestCode
             * @param config
             */
            fun openActivity(fragment: Fragment, requestCode: Int, config: RequestConfig?) {
                val intent = Intent(fragment.activity, ImageSelectorActivity::class.java)
                intent.putExtra(ImageSelector.KEY_CONFIG, config)
                fragment.startActivityForResult(intent, requestCode)
            }
    
            /**
             * 启动图片选择器
             * @param fragment
             * @param requestCode
             * @param config
             */
            fun openActivity(fragment: android.app.Fragment, requestCode: Int, config: RequestConfig?) {
                val intent = Intent(fragment.activity, ImageSelectorActivity::class.java)
                intent.putExtra(ImageSelector.KEY_CONFIG, config)
                fragment.startActivityForResult(intent, requestCode)
            }
        }
    }

  • 相关阅读:
    Vue 中 KeepAlive 内置缓存使用
    22.10.29 CF-1294C
    为何海量计算机系毕业生“负债”报IT培训班?高校IT教育该如何变革?
    MATLAB算法实战应用案例精讲-【图像处理】机器视觉(基础篇)(十)
    我不建议你使用SELECT *
    AutoJs学习-跳一跳相关代码
    基于java的创意项目众筹平台的设计与开发计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
    小满Vue3第四十五章(Vue3 Web Components)
    alert() 函数功能
    十四、使用 Vue Router 开发单页应用(1)
  • 原文地址:https://blog.csdn.net/maoning20080808/article/details/128117958